/*
 * qTip2 - Pretty powerful tooltips - @@vVERSION
 * http://qtip2.com
 *
 * Copyright (c) 2014 Craig Michael Thompson
 * Released under the MIT, GPL licenses
 * http://jquery.org/license
 *
 * Date: Fri Jul 25 2014 01:12 EDT-0400
@@BUILDPROPS */
/*global window: false, jQuery: false, console: false, define: false */

/* Cache window, document, undefined */
(function (window, document, undefined) {
  // Uses AMD or browser globals to create a jQuery plugin.
  (function (factory) {
    "use strict";
    if (typeof define === "function" && define.amd) {
      define(["jquery"], factory);
    } else if (jQuery && !jQuery.fn.qtip) {
      factory(jQuery);
    }
  })(function ($) {
    "use strict"; // Enable ECMAScript "strict" operation for this function. See more: http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/ // Munge the primitives - Paul Irish tip

    var TRUE = true,
      FALSE = false,
      NULL = null,
      // Common variables
      X = "x",
      Y = "y",
      WIDTH = "width",
      HEIGHT = "height",
      // Positioning sides
      TOP = "top",
      LEFT = "left",
      BOTTOM = "bottom",
      RIGHT = "right",
      CENTER = "center",
      // Position adjustment types
      FLIP = "flip",
      FLIPINVERT = "flipinvert",
      SHIFT = "shift",
      // Shortcut vars
      QTIP,
      PROTOTYPE,
      CORNER,
      CHECKS,
      PLUGINS = {},
      NAMESPACE = "qtip",
      ATTR_HAS = "data-hasqtip",
      ATTR_ID = "data-qtip-id",
      WIDGET = ["ui-widget", "ui-tooltip"],
      SELECTOR = "." + NAMESPACE,
      INACTIVE_EVENTS =
        "click dblclick mousedown mouseup mousemove mouseleave mouseenter".split(
          " "
        ),
      CLASS_FIXED = NAMESPACE + "-fixed",
      CLASS_DEFAULT = NAMESPACE + "-default",
      CLASS_FOCUS = NAMESPACE + "-focus",
      CLASS_HOVER = NAMESPACE + "-hover",
      CLASS_DISABLED = NAMESPACE + "-disabled",
      replaceSuffix = "_replacedByqTip",
      oldtitle = "oldtitle",
      trackingBound,
      // Browser detection
      BROWSER = {
        /*
         * IE version detection
         *
         * Adapted from: http://ajaxian.com/archives/attack-of-the-ie-conditional-comment
         * Credit to James Padolsey for the original implemntation!
         */
        ie: (function () {
          var v = 3,
            div = document.createElement("div");
          while (
            (div.innerHTML = "<!--[if gt IE " + ++v + "]><i></i><![endif]-->")
          ) {
            if (!div.getElementsByTagName("i")[0]) {
              break;
            }
          }
          return v > 4 ? v : NaN;
        })(),

        /*
         * iOS version detection
         */
        iOS:
          parseFloat(
            (
              "" +
              (/CPU.*OS ([0-9_]{1,5})|(CPU like).*AppleWebKit.*Mobile/i.exec(
                navigator.userAgent
              ) || [0, ""])[1]
            )
              .replace("undefined", "3_2")
              .replace("_", ".")
              .replace("_", "")
          ) || FALSE,
      };

    function QTip(target, options, id, attr) {
      // Elements and ID
      this.id = id;
      this.target = target;
      this.tooltip = NULL;
      this.elements = { target: target };

      // Internal constructs
      this._id = NAMESPACE + "-" + id;
      this.timers = { img: {} };
      this.options = options;
      this.plugins = {};

      // Cache object
      this.cache = {
        event: {},
        target: $(),
        disabled: FALSE,
        attr: attr,
        onTooltip: FALSE,
        lastClass: "",
      };

      // Set the initial flags
      this.rendered =
        this.destroyed =
        this.disabled =
        this.waiting =
        this.hiddenDuringWait =
        this.positioning =
        this.triggering =
          FALSE;
    }
    PROTOTYPE = QTip.prototype;

    PROTOTYPE._when = function (deferreds) {
      return $.when.apply($, deferreds);
    };

    PROTOTYPE.render = function (show) {
      if (this.rendered || this.destroyed) {
        return this;
      } // If tooltip has already been rendered, exit

      var self = this,
        options = this.options,
        cache = this.cache,
        elements = this.elements,
        text = options.content.text,
        title = options.content.title,
        button = options.content.button,
        posOptions = options.position,
        namespace = "." + this._id + " ",
        deferreds = [],
        tooltip;

      // Add ARIA attributes to target
      $.attr(this.target[0], "aria-describedby", this._id);

      // Create tooltip element
      this.tooltip =
        elements.tooltip =
        tooltip =
          $("<div/>", {
            id: this._id,
            class: [
              NAMESPACE,
              CLASS_DEFAULT,
              options.style.classes,
              NAMESPACE + "-pos-" + options.position.my.abbrev(),
            ].join(" "),
            width: options.style.width || "",
            height: options.style.height || "",
            tracking: posOptions.target === "mouse" && posOptions.adjust.mouse,

            /* ARIA specific attributes */
            role: "alert",
            "aria-live": "polite",
            "aria-atomic": FALSE,
            "aria-describedby": this._id + "-content",
            "aria-hidden": TRUE,
          })
            .toggleClass(CLASS_DISABLED, this.disabled)
            .attr(ATTR_ID, this.id)
            .data(NAMESPACE, this)
            .appendTo(posOptions.container)
            .append(
              // Create content element
              (elements.content = $("<div />", {
                class: NAMESPACE + "-content",
                id: this._id + "-content",
                "aria-atomic": TRUE,
              }))
            );

      // Set rendered flag and prevent redundant reposition calls for now
      this.rendered = -1;
      this.positioning = TRUE;

      // Create title...
      if (title) {
        this._createTitle();

        // Update title only if its not a callback (called in toggle if so)
        if (!$.isFunction(title)) {
          deferreds.push(this._updateTitle(title, FALSE));
        }
      }

      // Create button
      if (button) {
        this._createButton();
      }

      // Set proper rendered flag and update content if not a callback function (called in toggle)
      if (!$.isFunction(text)) {
        deferreds.push(this._updateContent(text, FALSE));
      }
      this.rendered = TRUE;

      // Setup widget classes
      this._setWidget();

      // Initialize 'render' plugins
      $.each(PLUGINS, function (name) {
        var instance;
        if (this.initialize === "render" && (instance = this(self))) {
          self.plugins[name] = instance;
        }
      });

      // Unassign initial events and assign proper events
      this._unassignEvents();
      this._assignEvents();

      // When deferreds have completed
      this._when(deferreds).then(function () {
        // tooltiprender event
        self._trigger("render");

        // Reset flags
        self.positioning = FALSE;

        // Show tooltip if not hidden during wait period
        if (!self.hiddenDuringWait && (options.show.ready || show)) {
          self.toggle(TRUE, cache.event, FALSE);
        }
        self.hiddenDuringWait = FALSE;
      });

      // Expose API
      QTIP.api[this.id] = this;

      return this;
    };

    PROTOTYPE.destroy = function (immediate) {
      // Set flag the signify destroy is taking place to plugins
      // and ensure it only gets destroyed once!
      if (this.destroyed) {
        return this.target;
      }

      function process() {
        if (this.destroyed) {
          return;
        }
        this.destroyed = TRUE;

        var target = this.target,
          title = target.attr(oldtitle);

        // Destroy tooltip if rendered
        if (this.rendered) {
          this.tooltip.stop(1, 0).find("*").remove().end().remove();
        }

        // Destroy all plugins
        $.each(this.plugins, function (name) {
          this.destroy && this.destroy();
        });

        // Clear timers and remove bound events
        clearTimeout(this.timers.show);
        clearTimeout(this.timers.hide);
        this._unassignEvents();

        // Remove api object and ARIA attributes
        target
          .removeData(NAMESPACE)
          .removeAttr(ATTR_ID)
          .removeAttr(ATTR_HAS)
          .removeAttr("aria-describedby");

        // Reset old title attribute if removed
        if (this.options.suppress && title) {
          target.attr("title", title).removeAttr(oldtitle);
        }

        // Remove qTip events associated with this API
        this._unbind(target);

        // Remove ID from used id objects, and delete object references
        // for better garbage collection and leak protection
        this.options =
          this.elements =
          this.cache =
          this.timers =
          this.plugins =
          this.mouse =
            NULL;

        // Delete epoxsed API object
        delete QTIP.api[this.id];
      }

      // If an immediate destory is needed
      if ((immediate !== TRUE || this.triggering === "hide") && this.rendered) {
        this.tooltip.one("tooltiphidden", $.proxy(process, this));
        !this.triggering && this.hide();
      }

      // If we're not in the process of hiding... process
      else {
        process.call(this);
      }

      return this.target;
    };

    function invalidOpt(a) {
      return a === NULL || $.type(a) !== "object";
    }

    function invalidContent(c) {
      return !(
        $.isFunction(c) ||
        (c && c.attr) ||
        c.length ||
        ($.type(c) === "object" && (c.jquery || c.then))
      );
    }

    // Option object sanitizer
    function sanitizeOptions(opts) {
      var content, text, ajax, once;

      if (invalidOpt(opts)) {
        return FALSE;
      }

      if (invalidOpt(opts.metadata)) {
        opts.metadata = { type: opts.metadata };
      }

      if ("content" in opts) {
        content = opts.content;

        if (invalidOpt(content) || content.jquery || content.done) {
          content = opts.content = {
            text: (text = invalidContent(content) ? FALSE : content),
          };
        } else {
          text = content.text;
        }

        // DEPRECATED - Old content.ajax plugin functionality
        // Converts it into the proper Deferred syntax
        if ("ajax" in content) {
          ajax = content.ajax;
          once = ajax && ajax.once !== FALSE;
          delete content.ajax;

          content.text = function (event, api) {
            var loading =
                text || $(this).attr(api.options.content.attr) || "Loading...",
              deferred = $.ajax($.extend({}, ajax, { context: api }))
                .then(ajax.success, NULL, ajax.error)
                .then(
                  function (content) {
                    if (content && once) {
                      api.set("content.text", content);
                    }
                    return content;
                  },
                  function (xhr, status, error) {
                    if (api.destroyed || xhr.status === 0) {
                      return;
                    }
                    api.set("content.text", status + ": " + error);
                  }
                );

            return !once
              ? (api.set("content.text", loading), deferred)
              : loading;
          };
        }

        if ("title" in content) {
          if (!invalidOpt(content.title)) {
            content.button = content.title.button;
            content.title = content.title.text;
          }

          if (invalidContent(content.title || FALSE)) {
            content.title = FALSE;
          }
        }
      }

      if ("position" in opts && invalidOpt(opts.position)) {
        opts.position = { my: opts.position, at: opts.position };
      }

      if ("show" in opts && invalidOpt(opts.show)) {
        opts.show = opts.show.jquery
          ? { target: opts.show }
          : opts.show === TRUE
          ? { ready: TRUE }
          : { event: opts.show };
      }

      if ("hide" in opts && invalidOpt(opts.hide)) {
        opts.hide = opts.hide.jquery
          ? { target: opts.hide }
          : { event: opts.hide };
      }

      if ("style" in opts && invalidOpt(opts.style)) {
        opts.style = { classes: opts.style };
      }

      // Sanitize plugin options
      $.each(PLUGINS, function () {
        this.sanitize && this.sanitize(opts);
      });

      return opts;
    }

    // Setup builtin .set() option checks
    CHECKS = PROTOTYPE.checks = {
      builtin: {
        // Core checks
        "^id$": function (obj, o, v, prev) {
          var id = v === TRUE ? QTIP.nextid : v,
            new_id = NAMESPACE + "-" + id;

          if (id !== FALSE && id.length > 0 && !$("#" + new_id).length) {
            this._id = new_id;

            if (this.rendered) {
              this.tooltip[0].id = this._id;
              this.elements.content[0].id = this._id + "-content";
              this.elements.title[0].id = this._id + "-title";
            }
          } else {
            obj[o] = prev;
          }
        },
        "^prerender": function (obj, o, v) {
          v && !this.rendered && this.render(this.options.show.ready);
        },

        // Content checks
        "^content.text$": function (obj, o, v) {
          this._updateContent(v);
        },
        "^content.attr$": function (obj, o, v, prev) {
          if (this.options.content.text === this.target.attr(prev)) {
            this._updateContent(this.target.attr(v));
          }
        },
        "^content.title$": function (obj, o, v) {
          // Remove title if content is null
          if (!v) {
            return this._removeTitle();
          }

          // If title isn't already created, create it now and update
          v && !this.elements.title && this._createTitle();
          this._updateTitle(v);
        },
        "^content.button$": function (obj, o, v) {
          this._updateButton(v);
        },
        "^content.title.(text|button)$": function (obj, o, v) {
          this.set("content." + o, v); // Backwards title.text/button compat
        },

        // Position checks
        "^position.(my|at)$": function (obj, o, v) {
          "string" === typeof v && (obj[o] = new CORNER(v, o === "at"));
        },
        "^position.container$": function (obj, o, v) {
          this.rendered && this.tooltip.appendTo(v);
        },

        // Show checks
        "^show.ready$": function (obj, o, v) {
          v && ((!this.rendered && this.render(TRUE)) || this.toggle(TRUE));
        },

        // Style checks
        "^style.classes$": function (obj, o, v, p) {
          this.rendered && this.tooltip.removeClass(p).addClass(v);
        },
        "^style.(width|height)": function (obj, o, v) {
          this.rendered && this.tooltip.css(o, v);
        },
        "^style.widget|content.title": function () {
          this.rendered && this._setWidget();
        },
        "^style.def": function (obj, o, v) {
          this.rendered && this.tooltip.toggleClass(CLASS_DEFAULT, !!v);
        },

        // Events check
        "^events.(render|show|move|hide|focus|blur)$": function (obj, o, v) {
          this.rendered &&
            this.tooltip[($.isFunction(v) ? "" : "un") + "bind"](
              "tooltip" + o,
              v
            );
        },

        // Properties which require event reassignment
        "^(show|hide|position).(event|target|fixed|inactive|leave|distance|viewport|adjust)":
          function () {
            if (!this.rendered) {
              return;
            }

            // Set tracking flag
            var posOptions = this.options.position;
            this.tooltip.attr(
              "tracking",
              posOptions.target === "mouse" && posOptions.adjust.mouse
            );

            // Reassign events
            this._unassignEvents();
            this._assignEvents();
          },
      },
    };

    // Dot notation converter
    function convertNotation(options, notation) {
      var i = 0,
        obj,
        option = options,
        // Split notation into array
        levels = notation.split(".");

      // Loop through
      while ((option = option[levels[i++]])) {
        if (i < levels.length) {
          obj = option;
        }
      }

      return [obj || options, levels.pop()];
    }

    PROTOTYPE.get = function (notation) {
      if (this.destroyed) {
        return this;
      }

      var o = convertNotation(this.options, notation.toLowerCase()),
        result = o[0][o[1]];

      return result.precedance ? result.string() : result;
    };

    function setCallback(notation, args) {
      var category, rule, match;

      for (category in this.checks) {
        for (rule in this.checks[category]) {
          if ((match = new RegExp(rule, "i").exec(notation))) {
            args.push(match);

            if (category === "builtin" || this.plugins[category]) {
              this.checks[category][rule].apply(
                this.plugins[category] || this,
                args
              );
            }
          }
        }
      }
    }

    var rmove =
        /^position\.(my|at|adjust|target|container|viewport)|style|content|show\.ready/i,
      rrender = /^prerender|show\.ready/i;

    PROTOTYPE.set = function (option, value) {
      if (this.destroyed) {
        return this;
      }

      var rendered = this.rendered,
        reposition = FALSE,
        options = this.options,
        checks = this.checks,
        name;

      // Convert singular option/value pair into object form
      if ("string" === typeof option) {
        name = option;
        option = {};
        option[name] = value;
      } else {
        option = $.extend({}, option);
      }

      // Set all of the defined options to their new values
      $.each(option, function (notation, value) {
        if (rendered && rrender.test(notation)) {
          delete option[notation];
          return;
        }

        // Set new obj value
        var obj = convertNotation(options, notation.toLowerCase()),
          previous;
        previous = obj[0][obj[1]];
        obj[0][obj[1]] = value && value.nodeType ? $(value) : value;

        // Also check if we need to reposition
        reposition = rmove.test(notation) || reposition;

        // Set the new params for the callback
        option[notation] = [obj[0], obj[1], value, previous];
      });

      // Re-sanitize options
      sanitizeOptions(options);

      /*
       * Execute any valid callbacks for the set options
       * Also set positioning flag so we don't get loads of redundant repositioning calls.
       */
      this.positioning = TRUE;
      $.each(option, $.proxy(setCallback, this));
      this.positioning = FALSE;

      // Update position if needed
      if (this.rendered && this.tooltip[0].offsetWidth > 0 && reposition) {
        this.reposition(
          options.position.target === "mouse" ? NULL : this.cache.event
        );
      }

      return this;
    };

    PROTOTYPE._update = function (content, element, reposition) {
      var self = this,
        cache = this.cache;

      // Make sure tooltip is rendered and content is defined. If not return
      if (!this.rendered || !content) {
        return FALSE;
      }

      // Use function to parse content
      if ($.isFunction(content)) {
        content = content.call(this.elements.target, cache.event, this) || "";
      }

      // Handle deferred content
      if ($.isFunction(content.then)) {
        cache.waiting = TRUE;
        return content.then(
          function (c) {
            cache.waiting = FALSE;
            return self._update(c, element);
          },
          NULL,
          function (e) {
            return self._update(e, element);
          }
        );
      }

      // If content is null... return false
      if (content === FALSE || (!content && content !== "")) {
        return FALSE;
      }

      // Append new content if its a DOM array and show it if hidden
      if (content.jquery && content.length > 0) {
        element
          .empty()
          .append(content.css({ display: "block", visibility: "visible" }));
      }

      // Content is a regular string, insert the new content
      else {
        element.html(content);
      }

      // Wait for content to be loaded, and reposition
      return this._waitForContent(element).then(function (images) {
        if (
          images.images &&
          images.images.length &&
          self.rendered &&
          self.tooltip[0].offsetWidth > 0
        ) {
          self.reposition(cache.event, !images.length);
        }
      });
    };

    PROTOTYPE._waitForContent = function (element) {
      var cache = this.cache;

      // Set flag
      cache.waiting = TRUE;

      // If imagesLoaded is included, ensure images have loaded and return promise
      return (
        $.fn.imagesLoaded ? element.imagesLoaded() : $.Deferred().resolve([])
      )
        .done(function () {
          cache.waiting = FALSE;
        })
        .promise();
    };

    PROTOTYPE._updateContent = function (content, reposition) {
      this._update(content, this.elements.content, reposition);
    };

    PROTOTYPE._updateTitle = function (content, reposition) {
      if (this._update(content, this.elements.title, reposition) === FALSE) {
        this._removeTitle(FALSE);
      }
    };

    PROTOTYPE._createTitle = function () {
      var elements = this.elements,
        id = this._id + "-title";

      // Destroy previous title element, if present
      if (elements.titlebar) {
        this._removeTitle();
      }

      // Create title bar and title elements
      elements.titlebar = $("<div />", {
        class:
          NAMESPACE +
          "-titlebar " +
          (this.options.style.widget ? createWidgetClass("header") : ""),
      })
        .append(
          (elements.title = $("<div />", {
            id: id,
            class: NAMESPACE + "-title",
            "aria-atomic": TRUE,
          }))
        )
        .insertBefore(elements.content)

        // Button-specific events
        .delegate(
          ".qtip-close",
          "mousedown keydown mouseup keyup mouseout",
          function (event) {
            $(this).toggleClass(
              "ui-state-active ui-state-focus",
              event.type.substr(-4) === "down"
            );
          }
        )
        .delegate(".qtip-close", "mouseover mouseout", function (event) {
          $(this).toggleClass("ui-state-hover", event.type === "mouseover");
        });

      // Create button if enabled
      if (this.options.content.button) {
        this._createButton();
      }
    };

    PROTOTYPE._removeTitle = function (reposition) {
      var elements = this.elements;

      if (elements.title) {
        elements.titlebar.remove();
        elements.titlebar = elements.title = elements.button = NULL;

        // Reposition if enabled
        if (reposition !== FALSE) {
          this.reposition();
        }
      }
    };

    PROTOTYPE.reposition = function (event, effect) {
      if (!this.rendered || this.positioning || this.destroyed) {
        return this;
      }

      // Set positioning flag
      this.positioning = TRUE;

      var cache = this.cache,
        tooltip = this.tooltip,
        posOptions = this.options.position,
        target = posOptions.target,
        my = posOptions.my,
        at = posOptions.at,
        viewport = posOptions.viewport,
        container = posOptions.container,
        adjust = posOptions.adjust,
        method = adjust.method.split(" "),
        tooltipWidth = tooltip.outerWidth(FALSE),
        tooltipHeight = tooltip.outerHeight(FALSE),
        targetWidth = 0,
        targetHeight = 0,
        type = tooltip.css("position"),
        position = { left: 0, top: 0 },
        visible = tooltip[0].offsetWidth > 0,
        isScroll = event && event.type === "scroll",
        win = $(window),
        doc = container[0].ownerDocument,
        mouse = this.mouse,
        pluginCalculations,
        offset;

      // Check if absolute position was passed
      if ($.isArray(target) && target.length === 2) {
        // Force left top and set position
        at = { x: LEFT, y: TOP };
        position = { left: target[0], top: target[1] };
      }

      // Check if mouse was the target
      else if (target === "mouse") {
        // Force left top to allow flipping
        at = { x: LEFT, y: TOP };

        // Use the cached mouse coordinates if available, or passed event has no coordinates
        if (mouse && mouse.pageX && (adjust.mouse || !event || !event.pageX)) {
          event = mouse;
        }

        // If the passed event has no coordinates (such as a scroll event)
        else if (!event || !event.pageX) {
          // Use the mouse origin that caused the show event, if distance hiding is enabled
          if (
            (!adjust.mouse || this.options.show.distance) &&
            cache.origin &&
            cache.origin.pageX
          ) {
            event = cache.origin;
          }

          // Use cached event for resize/scroll events
          else if (
            !event ||
            (event && (event.type === "resize" || event.type === "scroll"))
          ) {
            event = cache.event;
          }
        }

        // Calculate body and container offset and take them into account below
        if (type !== "static") {
          position = container.offset();
        }
        if (
          doc.body.offsetWidth !==
          (window.innerWidth || doc.documentElement.clientWidth)
        ) {
          offset = $(document.body).offset();
        }

        // Use event coordinates for position
        position = {
          left: event.pageX - position.left + ((offset && offset.left) || 0),
          top: event.pageY - position.top + ((offset && offset.top) || 0),
        };

        // Scroll events are a pain, some browsers
        if (adjust.mouse && isScroll && mouse) {
          position.left -= (mouse.scrollX || 0) - win.scrollLeft();
          position.top -= (mouse.scrollY || 0) - win.scrollTop();
        }
      }

      // Target wasn't mouse or absolute...
      else {
        // Check if event targetting is being used
        if (target === "event") {
          if (
            event &&
            event.target &&
            event.type !== "scroll" &&
            event.type !== "resize"
          ) {
            cache.target = $(event.target);
          } else if (!event.target) {
            cache.target = this.elements.target;
          }
        } else if (target !== "event") {
          cache.target = $(target.jquery ? target : this.elements.target);
        }
        target = cache.target;

        // Parse the target into a jQuery object and make sure there's an element present
        target = $(target).eq(0);
        if (target.length === 0) {
          return this;
        }

        // Check if window or document is the target
        else if (target[0] === document || target[0] === window) {
          targetWidth = BROWSER.iOS ? window.innerWidth : target.width();
          targetHeight = BROWSER.iOS ? window.innerHeight : target.height();

          if (target[0] === window) {
            position = {
              top: (viewport || target).scrollTop(),
              left: (viewport || target).scrollLeft(),
            };
          }
        }

        // Check if the target is an <AREA> element
        else if (PLUGINS.imagemap && target.is("area")) {
          pluginCalculations = PLUGINS.imagemap(
            this,
            target,
            at,
            PLUGINS.viewport ? method : FALSE
          );
        }

        // Check if the target is an SVG element
        else if (PLUGINS.svg && target && target[0].ownerSVGElement) {
          pluginCalculations = PLUGINS.svg(
            this,
            target,
            at,
            PLUGINS.viewport ? method : FALSE
          );
        }

        // Otherwise use regular jQuery methods
        else {
          targetWidth = target.outerWidth(FALSE);
          targetHeight = target.outerHeight(FALSE);
          position = target.offset();
        }

        // Parse returned plugin values into proper variables
        if (pluginCalculations) {
          targetWidth = pluginCalculations.width;
          targetHeight = pluginCalculations.height;
          offset = pluginCalculations.offset;
          position = pluginCalculations.position;
        }

        // Adjust position to take into account offset parents
        position = this.reposition.offset(target, position, container);

        // Adjust for position.fixed tooltips (and also iOS scroll bug in v3.2-4.0 & v4.3-4.3.2)
        if (
          (BROWSER.iOS > 3.1 && BROWSER.iOS < 4.1) ||
          (BROWSER.iOS >= 4.3 && BROWSER.iOS < 4.33) ||
          (!BROWSER.iOS && type === "fixed")
        ) {
          position.left -= win.scrollLeft();
          position.top -= win.scrollTop();
        }

        // Adjust position relative to target
        if (
          !pluginCalculations ||
          (pluginCalculations && pluginCalculations.adjustable !== FALSE)
        ) {
          position.left +=
            at.x === RIGHT
              ? targetWidth
              : at.x === CENTER
              ? targetWidth / 2
              : 0;
          position.top +=
            at.y === BOTTOM
              ? targetHeight
              : at.y === CENTER
              ? targetHeight / 2
              : 0;
        }
      }

      // Adjust position relative to tooltip
      position.left +=
        adjust.x +
        (my.x === RIGHT
          ? -tooltipWidth
          : my.x === CENTER
          ? -tooltipWidth / 2
          : 0);
      position.top +=
        adjust.y +
        (my.y === BOTTOM
          ? -tooltipHeight
          : my.y === CENTER
          ? -tooltipHeight / 2
          : 0);

      // Use viewport adjustment plugin if enabled
      if (PLUGINS.viewport) {
        position.adjusted = PLUGINS.viewport(
          this,
          position,
          posOptions,
          targetWidth,
          targetHeight,
          tooltipWidth,
          tooltipHeight
        );

        // Apply offsets supplied by positioning plugin (if used)
        if (offset && position.adjusted.left) {
          position.left += offset.left;
        }
        if (offset && position.adjusted.top) {
          position.top += offset.top;
        }
      }

      // Viewport adjustment is disabled, set values to zero
      else {
        position.adjusted = { left: 0, top: 0 };
      }

      // tooltipmove event
      if (
        !this._trigger("move", [position, viewport.elem || viewport], event)
      ) {
        return this;
      }
      delete position.adjusted;

      // If effect is disabled, target it mouse, no animation is defined or positioning gives NaN out, set CSS directly
      if (
        effect === FALSE ||
        !visible ||
        isNaN(position.left) ||
        isNaN(position.top) ||
        target === "mouse" ||
        !$.isFunction(posOptions.effect)
      ) {
        tooltip.css(position);
      }

      // Use custom function if provided
      else if ($.isFunction(posOptions.effect)) {
        posOptions.effect.call(tooltip, this, $.extend({}, position));
        tooltip.queue(function (next) {
          // Reset attributes to avoid cross-browser rendering bugs
          $(this).css({ opacity: "", height: "" });
          if (BROWSER.ie) {
            this.style.removeAttribute("filter");
          }

          next();
        });
      }

      // Set positioning flag
      this.positioning = FALSE;

      return this;
    };

    // Custom (more correct for qTip!) offset calculator
    PROTOTYPE.reposition.offset = function (elem, pos, container) {
      if (!container[0]) {
        return pos;
      }

      var ownerDocument = $(elem[0].ownerDocument),
        quirks = !!BROWSER.ie && document.compatMode !== "CSS1Compat",
        parent = container[0],
        scrolled,
        position,
        parentOffset,
        overflow;

      function scroll(e, i) {
        pos.left += i * e.scrollLeft();
        pos.top += i * e.scrollTop();
      }

      // Compensate for non-static containers offset
      do {
        if ((position = $.css(parent, "position")) !== "static") {
          if (position === "fixed") {
            parentOffset = parent.getBoundingClientRect();
            scroll(ownerDocument, -1);
          } else {
            parentOffset = $(parent).position();
            parentOffset.left +=
              parseFloat($.css(parent, "borderLeftWidth")) || 0;
            parentOffset.top +=
              parseFloat($.css(parent, "borderTopWidth")) || 0;
          }

          pos.left -=
            parentOffset.left + (parseFloat($.css(parent, "marginLeft")) || 0);
          pos.top -=
            parentOffset.top + (parseFloat($.css(parent, "marginTop")) || 0);

          // If this is the first parent element with an overflow of "scroll" or "auto", store it
          if (
            !scrolled &&
            (overflow = $.css(parent, "overflow")) !== "hidden" &&
            overflow !== "visible"
          ) {
            scrolled = $(parent);
          }
        }
      } while ((parent = parent.offsetParent));

      // Compensate for containers scroll if it also has an offsetParent (or in IE quirks mode)
      if (scrolled && (scrolled[0] !== ownerDocument[0] || quirks)) {
        scroll(scrolled, 1);
      }

      return pos;
    };

    // Corner class
    var C = (CORNER = PROTOTYPE.reposition.Corner =
      function (corner, forceY) {
        corner = ("" + corner)
          .replace(/([A-Z])/, " $1")
          .replace(/middle/gi, CENTER)
          .toLowerCase();
        this.x = (corner.match(/left|right/i) ||
          corner.match(/center/) || ["inherit"])[0].toLowerCase();
        this.y = (corner.match(/top|bottom|center/i) || [
          "inherit",
        ])[0].toLowerCase();
        this.forceY = !!forceY;

        var f = corner.charAt(0);
        this.precedance = f === "t" || f === "b" ? Y : X;
      }).prototype;

    C.invert = function (z, center) {
      this[z] =
        this[z] === LEFT ? RIGHT : this[z] === RIGHT ? LEFT : center || this[z];
    };

    C.string = function () {
      var x = this.x,
        y = this.y;
      return x === y
        ? x
        : this.precedance === Y || (this.forceY && y !== "center")
        ? y + " " + x
        : x + " " + y;
    };

    C.abbrev = function () {
      var result = this.string().split(" ");
      return result[0].charAt(0) + ((result[1] && result[1].charAt(0)) || "");
    };

    C.clone = function () {
      return new CORNER(this.string(), this.forceY);
    };
    PROTOTYPE.toggle = function (state, event) {
      var cache = this.cache,
        options = this.options,
        tooltip = this.tooltip;

      // Try to prevent flickering when tooltip overlaps show element
      if (event) {
        if (
          /over|enter/.test(event.type) &&
          /out|leave/.test(cache.event.type) &&
          options.show.target.add(event.target).length ===
            options.show.target.length &&
          tooltip.has(event.relatedTarget).length
        ) {
          return this;
        }

        // Cache event
        cache.event = cloneEvent(event);
      }

      // If we're currently waiting and we've just hidden... stop it
      this.waiting && !state && (this.hiddenDuringWait = TRUE);

      // Render the tooltip if showing and it isn't already
      if (!this.rendered) {
        return state ? this.render(1) : this;
      } else if (this.destroyed || this.disabled) {
        return this;
      }

      var type = state ? "show" : "hide",
        opts = this.options[type],
        otherOpts = this.options[!state ? "show" : "hide"],
        posOptions = this.options.position,
        contentOptions = this.options.content,
        width = this.tooltip.css("width"),
        visible = this.tooltip.is(":visible"),
        animate = state || opts.target.length === 1,
        sameTarget =
          !event || opts.target.length < 2 || cache.target[0] === event.target,
        identicalState,
        allow,
        showEvent,
        delay,
        after;

      // Detect state if valid one isn't provided
      if ((typeof state).search("boolean|number")) {
        state = !visible;
      }

      // Check if the tooltip is in an identical state to the new would-be state
      identicalState =
        !tooltip.is(":animated") && visible === state && sameTarget;

      // Fire tooltip(show/hide) event and check if destroyed
      allow = !identicalState ? !!this._trigger(type, [90]) : NULL;

      // Check to make sure the tooltip wasn't destroyed in the callback
      if (this.destroyed) {
        return this;
      }

      // If the user didn't stop the method prematurely and we're showing the tooltip, focus it
      if (allow !== FALSE && state) {
        this.focus(event);
      }

      // If the state hasn't changed or the user stopped it, return early
      if (!allow || identicalState) {
        return this;
      }

      // Set ARIA hidden attribute
      $.attr(tooltip[0], "aria-hidden", !!!state);

      // Execute state specific properties
      if (state) {
        // Store show origin coordinates
        cache.origin = cloneEvent(this.mouse);

        // Update tooltip content & title if it's a dynamic function
        if ($.isFunction(contentOptions.text)) {
          this._updateContent(contentOptions.text, FALSE);
        }
        if ($.isFunction(contentOptions.title)) {
          this._updateTitle(contentOptions.title, FALSE);
        }

        // Cache mousemove events for positioning purposes (if not already tracking)
        if (
          !trackingBound &&
          posOptions.target === "mouse" &&
          posOptions.adjust.mouse
        ) {
          $(document).bind("mousemove." + NAMESPACE, this._storeMouse);
          trackingBound = TRUE;
        }

        // Update the tooltip position (set width first to prevent viewport/max-width issues)
        if (!width) {
          tooltip.css("width", tooltip.outerWidth(FALSE));
        }
        this.reposition(event, arguments[2]);
        if (!width) {
          tooltip.css("width", "");
        }

        // Hide other tooltips if tooltip is solo
        if (!!opts.solo) {
          (typeof opts.solo === "string"
            ? $(opts.solo)
            : $(SELECTOR, opts.solo)
          )
            .not(tooltip)
            .not(opts.target)
            .qtip("hide", $.Event("tooltipsolo"));
        }
      } else {
        // Clear show timer if we're hiding
        clearTimeout(this.timers.show);

        // Remove cached origin on hide
        delete cache.origin;

        // Remove mouse tracking event if not needed (all tracking qTips are hidden)
        if (
          trackingBound &&
          !$(SELECTOR + '[tracking="true"]:visible', opts.solo).not(tooltip)
            .length
        ) {
          $(document).unbind("mousemove." + NAMESPACE);
          trackingBound = FALSE;
        }

        // Blur the tooltip
        this.blur(event);
      }

      // Define post-animation, state specific properties
      after = $.proxy(function () {
        if (state) {
          // Prevent antialias from disappearing in IE by removing filter
          if (BROWSER.ie) {
            tooltip[0].style.removeAttribute("filter");
          }

          // Remove overflow setting to prevent tip bugs
          tooltip.css("overflow", "");

          // Autofocus elements if enabled
          if ("string" === typeof opts.autofocus) {
            $(this.options.show.autofocus, tooltip).focus();
          }

          // If set, hide tooltip when inactive for delay period
          this.options.show.target.trigger("qtip-" + this.id + "-inactive");
        } else {
          // Reset CSS states
          tooltip.css({
            display: "",
            visibility: "",
            opacity: "",
            left: "",
            top: "",
          });
        }

        // tooltipvisible/tooltiphidden events
        this._trigger(state ? "visible" : "hidden");
      }, this);

      // If no effect type is supplied, use a simple toggle
      if (opts.effect === FALSE || animate === FALSE) {
        tooltip[type]();
        after();
      }

      // Use custom function if provided
      else if ($.isFunction(opts.effect)) {
        tooltip.stop(1, 1);
        opts.effect.call(tooltip, this);
        tooltip.queue("fx", function (n) {
          after();
          n();
        });
      }

      // Use basic fade function by default
      else {
        tooltip.fadeTo(90, state ? 1 : 0, after);
      }

      // If inactive hide method is set, active it
      if (state) {
        opts.target.trigger("qtip-" + this.id + "-inactive");
      }

      return this;
    };

    PROTOTYPE.show = function (event) {
      return this.toggle(TRUE, event);
    };

    PROTOTYPE.hide = function (event) {
      return this.toggle(FALSE, event);
    };

    PROTOTYPE.focus = function (event) {
      if (!this.rendered || this.destroyed) {
        return this;
      }

      var qtips = $(SELECTOR),
        tooltip = this.tooltip,
        curIndex = parseInt(tooltip[0].style.zIndex, 10),
        newIndex = QTIP.zindex + qtips.length,
        focusedElem;

      // Only update the z-index if it has changed and tooltip is not already focused
      if (!tooltip.hasClass(CLASS_FOCUS)) {
        // tooltipfocus event
        if (this._trigger("focus", [newIndex], event)) {
          // Only update z-index's if they've changed
          if (curIndex !== newIndex) {
            // Reduce our z-index's and keep them properly ordered
            qtips.each(function () {
              if (this.style.zIndex > curIndex) {
                this.style.zIndex = this.style.zIndex - 1;
              }
            });

            // Fire blur event for focused tooltip
            qtips.filter("." + CLASS_FOCUS).qtip("blur", event);
          }

          // Set the new z-index
          tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;
        }
      }

      return this;
    };

    PROTOTYPE.blur = function (event) {
      if (!this.rendered || this.destroyed) {
        return this;
      }

      // Set focused status to FALSE
      this.tooltip.removeClass(CLASS_FOCUS);

      // tooltipblur event
      this._trigger("blur", [this.tooltip.css("zIndex")], event);

      return this;
    };

    PROTOTYPE.disable = function (state) {
      if (this.destroyed) {
        return this;
      }

      // If 'toggle' is passed, toggle the current state
      if (state === "toggle") {
        state = !(this.rendered
          ? this.tooltip.hasClass(CLASS_DISABLED)
          : this.disabled);
      }

      // Disable if no state passed
      else if ("boolean" !== typeof state) {
        state = TRUE;
      }

      if (this.rendered) {
        this.tooltip
          .toggleClass(CLASS_DISABLED, state)
          .attr("aria-disabled", state);
      }

      this.disabled = !!state;

      return this;
    };

    PROTOTYPE.enable = function () {
      return this.disable(FALSE);
    };

    PROTOTYPE._createButton = function () {
      var self = this,
        elements = this.elements,
        tooltip = elements.tooltip,
        button = this.options.content.button,
        isString = typeof button === "string",
        close = isString ? button : "Close tooltip";

      if (elements.button) {
        elements.button.remove();
      }

      // Use custom button if one was supplied by user, else use default
      if (button.jquery) {
        elements.button = button;
      } else {
        elements.button = $("<a />", {
          class:
            "qtip-close " +
            (this.options.style.widget ? "" : NAMESPACE + "-icon"),
          title: close,
          "aria-label": close,
        }).prepend(
          $("<span />", {
            class: "ui-icon ui-icon-close",
            html: "&times;",
          })
        );
      }

      // Create button and setup attributes
      elements.button
        .appendTo(elements.titlebar || tooltip)
        .attr("role", "button")
        .click(function (event) {
          if (!tooltip.hasClass(CLASS_DISABLED)) {
            self.hide(event);
          }
          return FALSE;
        });
    };

    PROTOTYPE._updateButton = function (button) {
      // Make sure tooltip is rendered and if not, return
      if (!this.rendered) {
        return FALSE;
      }

      var elem = this.elements.button;
      if (button) {
        this._createButton();
      } else {
        elem.remove();
      }
    }; // Widget class creator

    function createWidgetClass(cls) {
      return WIDGET.concat("").join(cls ? "-" + cls + " " : " ");
    }

    // Widget class setter method
    PROTOTYPE._setWidget = function () {
      var on = this.options.style.widget,
        elements = this.elements,
        tooltip = elements.tooltip,
        disabled = tooltip.hasClass(CLASS_DISABLED);

      tooltip.removeClass(CLASS_DISABLED);
      CLASS_DISABLED = on ? "ui-state-disabled" : "qtip-disabled";
      tooltip.toggleClass(CLASS_DISABLED, disabled);

      tooltip
        .toggleClass("ui-helper-reset " + createWidgetClass(), on)
        .toggleClass(CLASS_DEFAULT, this.options.style.def && !on);

      if (elements.content) {
        elements.content.toggleClass(createWidgetClass("content"), on);
      }
      if (elements.titlebar) {
        elements.titlebar.toggleClass(createWidgetClass("header"), on);
      }
      if (elements.button) {
        elements.button.toggleClass(NAMESPACE + "-icon", !on);
      }
    };
    function cloneEvent(event) {
      return (
        (event && {
          type: event.type,
          pageX: event.pageX,
          pageY: event.pageY,
          target: event.target,
          relatedTarget: event.relatedTarget,
          scrollX:
            event.scrollX ||
            window.pageXOffset ||
            document.body.scrollLeft ||
            document.documentElement.scrollLeft,
          scrollY:
            event.scrollY ||
            window.pageYOffset ||
            document.body.scrollTop ||
            document.documentElement.scrollTop,
        }) ||
        {}
      );
    }

    function delay(callback, duration) {
      // If tooltip has displayed, start hide timer
      if (duration > 0) {
        return setTimeout($.proxy(callback, this), duration);
      } else {
        callback.call(this);
      }
    }

    function showMethod(event) {
      if (this.tooltip.hasClass(CLASS_DISABLED)) {
        return FALSE;
      }

      // Clear hide timers
      clearTimeout(this.timers.show);
      clearTimeout(this.timers.hide);

      // Start show timer
      this.timers.show = delay.call(
        this,
        function () {
          this.toggle(TRUE, event);
        },
        this.options.show.delay
      );
    }

    function hideMethod(event) {
      if (this.tooltip.hasClass(CLASS_DISABLED)) {
        return FALSE;
      }

      // Check if new target was actually the tooltip element
      var relatedTarget = $(event.relatedTarget),
        ontoTooltip = relatedTarget.closest(SELECTOR)[0] === this.tooltip[0],
        ontoTarget = relatedTarget[0] === this.options.show.target[0];

      // Clear timers and stop animation queue
      clearTimeout(this.timers.show);
      clearTimeout(this.timers.hide);

      // Prevent hiding if tooltip is fixed and event target is the tooltip.
      // Or if mouse positioning is enabled and cursor momentarily overlaps
      if (
        (this !== relatedTarget[0] &&
          this.options.position.target === "mouse" &&
          ontoTooltip) ||
        (this.options.hide.fixed &&
          /mouse(out|leave|move)/.test(event.type) &&
          (ontoTooltip || ontoTarget))
      ) {
        try {
          event.preventDefault();
          event.stopImmediatePropagation();
        } catch (e) {}

        return;
      }

      // If tooltip has displayed, start hide timer
      this.timers.hide = delay.call(
        this,
        function () {
          this.toggle(FALSE, event);
        },
        this.options.hide.delay,
        this
      );
    }

    function inactiveMethod(event) {
      if (
        this.tooltip.hasClass(CLASS_DISABLED) ||
        !this.options.hide.inactive
      ) {
        return FALSE;
      }

      // Clear timer
      clearTimeout(this.timers.inactive);

      this.timers.inactive = delay.call(
        this,
        function () {
          this.hide(event);
        },
        this.options.hide.inactive
      );
    }

    function repositionMethod(event) {
      if (this.rendered && this.tooltip[0].offsetWidth > 0) {
        this.reposition(event);
      }
    }

    // Store mouse coordinates
    PROTOTYPE._storeMouse = function (event) {
      (this.mouse = cloneEvent(event)).type = "mousemove";
    };

    // Bind events
    PROTOTYPE._bind = function (targets, events, method, suffix, context) {
      var ns = "." + this._id + (suffix ? "-" + suffix : "");
      events.length &&
        $(targets).bind(
          (events.split ? events : events.join(ns + " ")) + ns,
          $.proxy(method, context || this)
        );
    };
    PROTOTYPE._unbind = function (targets, suffix) {
      $(targets).unbind("." + this._id + (suffix ? "-" + suffix : ""));
    };

    // Apply common event handlers using delegate (avoids excessive .bind calls!)
    var ns = "." + NAMESPACE;
    function delegate(selector, events, method) {
      $(document.body).delegate(
        selector,
        (events.split ? events : events.join(ns + " ")) + ns,
        function () {
          var api = QTIP.api[$.attr(this, ATTR_ID)];
          api && !api.disabled && method.apply(api, arguments);
        }
      );
    }

    $(function () {
      delegate(SELECTOR, ["mouseenter", "mouseleave"], function (event) {
        var state = event.type === "mouseenter",
          tooltip = $(event.currentTarget),
          target = $(event.relatedTarget || event.target),
          options = this.options;

        // On mouseenter...
        if (state) {
          // Focus the tooltip on mouseenter (z-index stacking)
          this.focus(event);

          // Clear hide timer on tooltip hover to prevent it from closing
          tooltip.hasClass(CLASS_FIXED) &&
            !tooltip.hasClass(CLASS_DISABLED) &&
            clearTimeout(this.timers.hide);
        }

        // On mouseleave...
        else {
          // Hide when we leave the tooltip and not onto the show target (if a hide event is set)
          if (
            options.position.target === "mouse" &&
            options.hide.event &&
            options.show.target &&
            !target.closest(options.show.target[0]).length
          ) {
            this.hide(event);
          }
        }

        // Add hover class
        tooltip.toggleClass(CLASS_HOVER, state);
      });

      // Define events which reset the 'inactive' event handler
      delegate("[" + ATTR_ID + "]", INACTIVE_EVENTS, inactiveMethod);
    });

    // Event trigger
    PROTOTYPE._trigger = function (type, args, event) {
      var callback = $.Event("tooltip" + type);
      callback.originalEvent =
        (event && $.extend({}, event)) || this.cache.event || NULL;

      this.triggering = type;
      this.tooltip.trigger(callback, [this].concat(args || []));
      this.triggering = FALSE;

      return !callback.isDefaultPrevented();
    };

    PROTOTYPE._bindEvents = function (
      showEvents,
      hideEvents,
      showTarget,
      hideTarget,
      showMethod,
      hideMethod
    ) {
      // If hide and show targets are the same...
      if (hideTarget.add(showTarget).length === hideTarget.length) {
        var toggleEvents = [];

        // Filter identical show/hide events
        hideEvents = $.map(hideEvents, function (type) {
          var showIndex = $.inArray(type, showEvents);

          // Both events are identical, remove from both hide and show events
          // and append to toggleEvents
          if (showIndex > -1) {
            toggleEvents.push(showEvents.splice(showIndex, 1)[0]);
            return;
          }

          return type;
        });

        // Toggle events are special case of identical show/hide events, which happen in sequence
        toggleEvents.length &&
          this._bind(showTarget, toggleEvents, function (event) {
            var state = this.rendered ? this.tooltip[0].offsetWidth > 0 : false;
            (state ? hideMethod : showMethod).call(this, event);
          });
      }

      // Apply show/hide/toggle events
      this._bind(showTarget, showEvents, showMethod);
      this._bind(hideTarget, hideEvents, hideMethod);
    };

    PROTOTYPE._assignInitialEvents = function (event) {
      var options = this.options,
        showTarget = options.show.target,
        hideTarget = options.hide.target,
        showEvents = options.show.event
          ? $.trim("" + options.show.event).split(" ")
          : [],
        hideEvents = options.hide.event
          ? $.trim("" + options.hide.event).split(" ")
          : [];

      /*
       * Make sure hoverIntent functions properly by using mouseleave as a hide event if
       * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
       */
      if (
        /mouse(over|enter)/i.test(options.show.event) &&
        !/mouse(out|leave)/i.test(options.hide.event)
      ) {
        hideEvents.push("mouseleave");
      }

      /*
       * Also make sure initial mouse targetting works correctly by caching mousemove coords
       * on show targets before the tooltip has rendered. Also set onTarget when triggered to
       * keep mouse tracking working.
       */
      this._bind(showTarget, "mousemove", function (event) {
        this._storeMouse(event);
        this.cache.onTarget = TRUE;
      });

      // Define hoverIntent function
      function hoverIntent(event) {
        // Only continue if tooltip isn't disabled
        if (this.disabled || this.destroyed) {
          return FALSE;
        }

        // Cache the event data
        this.cache.event = cloneEvent(event);
        this.cache.target = event ? $(event.target) : [undefined];

        // Start the event sequence
        clearTimeout(this.timers.show);
        this.timers.show = delay.call(
          this,
          function () {
            this.render(typeof event === "object" || options.show.ready);
          },
          options.show.delay
        );
      }

      // Filter and bind events
      this._bindEvents(
        showEvents,
        hideEvents,
        showTarget,
        hideTarget,
        hoverIntent,
        function () {
          clearTimeout(this.timers.show);
        }
      );

      // Prerendering is enabled, create tooltip now
      if (options.show.ready || options.prerender) {
        hoverIntent.call(this, event);
      }
    };

    // Event assignment method
    PROTOTYPE._assignEvents = function () {
      var self = this,
        options = this.options,
        posOptions = options.position,
        tooltip = this.tooltip,
        showTarget = options.show.target,
        hideTarget = options.hide.target,
        containerTarget = posOptions.container,
        viewportTarget = posOptions.viewport,
        documentTarget = $(document),
        bodyTarget = $(document.body),
        windowTarget = $(window),
        showEvents = options.show.event
          ? $.trim("" + options.show.event).split(" ")
          : [],
        hideEvents = options.hide.event
          ? $.trim("" + options.hide.event).split(" ")
          : [];

      // Assign passed event callbacks
      $.each(options.events, function (name, callback) {
        self._bind(
          tooltip,
          name === "toggle"
            ? ["tooltipshow", "tooltiphide"]
            : ["tooltip" + name],
          callback,
          null,
          tooltip
        );
      });

      // Hide tooltips when leaving current window/frame (but not select/option elements)
      if (
        /mouse(out|leave)/i.test(options.hide.event) &&
        options.hide.leave === "window"
      ) {
        this._bind(documentTarget, ["mouseout", "blur"], function (event) {
          if (
            !/select|option/.test(event.target.nodeName) &&
            !event.relatedTarget
          ) {
            this.hide(event);
          }
        });
      }

      // Enable hide.fixed by adding appropriate class
      if (options.hide.fixed) {
        hideTarget = hideTarget.add(tooltip.addClass(CLASS_FIXED));
      } else if (/mouse(over|enter)/i.test(options.show.event)) {
        /*
         * Make sure hoverIntent functions properly by using mouseleave to clear show timer if
         * mouseenter/mouseout is used for show.event, even if it isn't in the users options.
         */
        this._bind(hideTarget, "mouseleave", function () {
          clearTimeout(this.timers.show);
        });
      }

      // Hide tooltip on document mousedown if unfocus events are enabled
      if (("" + options.hide.event).indexOf("unfocus") > -1) {
        this._bind(
          containerTarget.closest("html"),
          ["mousedown", "touchstart"],
          function (event) {
            var elem = $(event.target),
              enabled =
                this.rendered &&
                !this.tooltip.hasClass(CLASS_DISABLED) &&
                this.tooltip[0].offsetWidth > 0,
              isAncestor =
                elem.parents(SELECTOR).filter(this.tooltip[0]).length > 0;

            if (
              elem[0] !== this.target[0] &&
              elem[0] !== this.tooltip[0] &&
              !isAncestor &&
              !this.target.has(elem[0]).length &&
              enabled
            ) {
              this.hide(event);
            }
          }
        );
      }

      // Check if the tooltip hides when inactive
      if ("number" === typeof options.hide.inactive) {
        // Bind inactive method to show target(s) as a custom event
        this._bind(showTarget, "qtip-" + this.id + "-inactive", inactiveMethod);

        // Define events which reset the 'inactive' event handler
        this._bind(
          hideTarget.add(tooltip),
          QTIP.inactiveEvents,
          inactiveMethod,
          "-inactive"
        );
      }

      // Filter and bind events
      this._bindEvents(
        showEvents,
        hideEvents,
        showTarget,
        hideTarget,
        showMethod,
        hideMethod
      );

      // Mouse movement bindings
      this._bind(showTarget.add(tooltip), "mousemove", function (event) {
        // Check if the tooltip hides when mouse is moved a certain distance
        if ("number" === typeof options.hide.distance) {
          var origin = this.cache.origin || {},
            limit = this.options.hide.distance,
            abs = Math.abs;

          // Check if the movement has gone beyond the limit, and hide it if so
          if (
            abs(event.pageX - origin.pageX) >= limit ||
            abs(event.pageY - origin.pageY) >= limit
          ) {
            this.hide(event);
          }
        }

        // Cache mousemove coords on show targets
        this._storeMouse(event);
      });

      // Mouse positioning events
      if (posOptions.target === "mouse") {
        // If mouse adjustment is on...
        if (posOptions.adjust.mouse) {
          // Apply a mouseleave event so we don't get problems with overlapping
          if (options.hide.event) {
            // Track if we're on the target or not
            this._bind(
              showTarget,
              ["mouseenter", "mouseleave"],
              function (event) {
                this.cache.onTarget = event.type === "mouseenter";
              }
            );
          }

          // Update tooltip position on mousemove
          this._bind(documentTarget, "mousemove", function (event) {
            // Update the tooltip position only if the tooltip is visible and adjustment is enabled
            if (
              this.rendered &&
              this.cache.onTarget &&
              !this.tooltip.hasClass(CLASS_DISABLED) &&
              this.tooltip[0].offsetWidth > 0
            ) {
              this.reposition(event);
            }
          });
        }
      }

      // Adjust positions of the tooltip on window resize if enabled
      if (posOptions.adjust.resize || viewportTarget.length) {
        this._bind(
          $.event.special.resize ? viewportTarget : windowTarget,
          "resize",
          repositionMethod
        );
      }

      // Adjust tooltip position on scroll of the window or viewport element if present
      if (posOptions.adjust.scroll) {
        this._bind(
          windowTarget.add(posOptions.container),
          "scroll",
          repositionMethod
        );
      }
    };

    // Un-assignment method
    PROTOTYPE._unassignEvents = function () {
      var targets = [
        this.options.show.target[0],
        this.options.hide.target[0],
        this.rendered && this.tooltip[0],
        this.options.position.container[0],
        this.options.position.viewport[0],
        this.options.position.container.closest("html")[0], // unfocus
        window,
        document,
      ];

      this._unbind(
        $([]).pushStack(
          $.grep(targets, function (i) {
            return typeof i === "object";
          })
        )
      );
    }; // Initialization method

    function init(elem, id, opts) {
      var obj,
        posOptions,
        attr,
        config,
        title,
        // Setup element references
        docBody = $(document.body),
        // Use document body instead of document element if needed
        newTarget = elem[0] === document ? docBody : elem,
        // Grab metadata from element if plugin is present
        metadata = elem.metadata ? elem.metadata(opts.metadata) : NULL,
        // If metadata type if HTML5, grab 'name' from the object instead, or use the regular data object otherwise
        metadata5 =
          opts.metadata.type === "html5" && metadata
            ? metadata[opts.metadata.name]
            : NULL,
        // Grab data from metadata.name (or data-qtipopts as fallback) using .data() method,
        html5 = elem.data(opts.metadata.name || "qtipopts");

      // If we don't get an object returned attempt to parse it manualyl without parseJSON
      try {
        html5 = typeof html5 === "string" ? $.parseJSON(html5) : html5;
      } catch (e) {}

      // Merge in and sanitize metadata
      config = $.extend(
        TRUE,
        {},
        QTIP.defaults,
        opts,
        typeof html5 === "object" ? sanitizeOptions(html5) : NULL,
        sanitizeOptions(metadata5 || metadata)
      );

      // Re-grab our positioning options now we've merged our metadata and set id to passed value
      posOptions = config.position;
      config.id = id;

      // Setup missing content if none is detected
      if ("boolean" === typeof config.content.text) {
        attr = elem.attr(config.content.attr);

        // Grab from supplied attribute if available
        if (config.content.attr !== FALSE && attr) {
          config.content.text = attr;
        }

        // No valid content was found, abort render
        else {
          return FALSE;
        }
      }

      // Setup target options
      if (!posOptions.container.length) {
        posOptions.container = docBody;
      }
      if (posOptions.target === FALSE) {
        posOptions.target = newTarget;
      }
      if (config.show.target === FALSE) {
        config.show.target = newTarget;
      }
      if (config.show.solo === TRUE) {
        config.show.solo = posOptions.container.closest("body");
      }
      if (config.hide.target === FALSE) {
        config.hide.target = newTarget;
      }
      if (config.position.viewport === TRUE) {
        config.position.viewport = posOptions.container;
      }

      // Ensure we only use a single container
      posOptions.container = posOptions.container.eq(0);

      // Convert position corner values into x and y strings
      posOptions.at = new CORNER(posOptions.at, TRUE);
      posOptions.my = new CORNER(posOptions.my);

      // Destroy previous tooltip if overwrite is enabled, or skip element if not
      if (elem.data(NAMESPACE)) {
        if (config.overwrite) {
          elem.qtip("destroy", true);
        } else if (config.overwrite === FALSE) {
          return FALSE;
        }
      }

      // Add has-qtip attribute
      elem.attr(ATTR_HAS, id);

      // Remove title attribute and store it if present
      if (config.suppress && (title = elem.attr("title"))) {
        // Final attr call fixes event delegatiom and IE default tooltip showing problem
        elem.removeAttr("title").attr(oldtitle, title).attr("title", "");
      }

      // Initialize the tooltip and add API reference
      obj = new QTip(elem, config, id, !!attr);
      elem.data(NAMESPACE, obj);

      // Catch remove/removeqtip events on target element to destroy redundant tooltip
      elem.one("remove.qtip-" + id + " removeqtip.qtip-" + id, function () {
        var api;
        if ((api = $(this).data(NAMESPACE))) {
          api.destroy(true);
        }
      });

      return obj;
    }

    // jQuery $.fn extension method
    QTIP = $.fn.qtip = function (options, notation, newValue) {
      var command = ("" + options).toLowerCase(), // Parse command
        returned = NULL,
        args = $.makeArray(arguments).slice(1),
        event = args[args.length - 1],
        opts = this[0] ? $.data(this[0], NAMESPACE) : NULL;

      // Check for API request
      if ((!arguments.length && opts) || command === "api") {
        return opts;
      }

      // Execute API command if present
      else if ("string" === typeof options) {
        this.each(function () {
          var api = $.data(this, NAMESPACE);
          if (!api) {
            return TRUE;
          }

          // Cache the event if possible
          if (event && event.timeStamp) {
            api.cache.event = event;
          }

          // Check for specific API commands
          if (notation && (command === "option" || command === "options")) {
            if (newValue !== undefined || $.isPlainObject(notation)) {
              api.set(notation, newValue);
            } else {
              returned = api.get(notation);
              return FALSE;
            }
          }

          // Execute API command
          else if (api[command]) {
            api[command].apply(api, args);
          }
        });

        return returned !== NULL ? returned : this;
      }

      // No API commands. validate provided options and setup qTips
      else if ("object" === typeof options || !arguments.length) {
        // Sanitize options first
        opts = sanitizeOptions($.extend(TRUE, {}, options));

        return this.each(function (i) {
          var api, id;

          // Find next available ID, or use custom ID if provided
          id = $.isArray(opts.id) ? opts.id[i] : opts.id;
          id =
            !id || id === FALSE || id.length < 1 || QTIP.api[id]
              ? QTIP.nextid++
              : id;

          // Initialize the qTip and re-grab newly sanitized options
          api = init($(this), id, opts);
          if (api === FALSE) {
            return TRUE;
          } else {
            QTIP.api[id] = api;
          }

          // Initialize plugins
          $.each(PLUGINS, function () {
            if (this.initialize === "initialize") {
              this(api);
            }
          });

          // Assign initial pre-render events
          api._assignInitialEvents(event);
        });
      }
    };

    // Expose class
    $.qtip = QTip;

    // Populated in render method
    QTIP.api = {};
    $.each(
      {
        /* Allow other plugins to successfully retrieve the title of an element with a qTip applied */
        attr: function (attr, val) {
          if (this.length) {
            var self = this[0],
              title = "title",
              api = $.data(self, "qtip");

            if (
              attr === title &&
              api &&
              "object" === typeof api &&
              api.options.suppress
            ) {
              if (arguments.length < 2) {
                return $.attr(self, oldtitle);
              }

              // If qTip is rendered and title was originally used as content, update it
              if (api && api.options.content.attr === title && api.cache.attr) {
                api.set("content.text", val);
              }

              // Use the regular attr method to set, then cache the result
              return this.attr(oldtitle, val);
            }
          }

          return $.fn["attr" + replaceSuffix].apply(this, arguments);
        },

        /* Allow clone to correctly retrieve cached title attributes */
        clone: function (keepData) {
          var titles = $([]),
            title = "title",
            // Clone our element using the real clone method
            elems = $.fn["clone" + replaceSuffix].apply(this, arguments);

          // Grab all elements with an oldtitle set, and change it to regular title attribute, if keepData is false
          if (!keepData) {
            elems
              .filter("[" + oldtitle + "]")
              .attr("title", function () {
                return $.attr(this, oldtitle);
              })
              .removeAttr(oldtitle);
          }

          return elems;
        },
      },
      function (name, func) {
        if (!func || $.fn[name + replaceSuffix]) {
          return TRUE;
        }

        var old = ($.fn[name + replaceSuffix] = $.fn[name]);
        $.fn[name] = function () {
          return func.apply(this, arguments) || old.apply(this, arguments);
        };
      }
    );

    /* Fire off 'removeqtip' handler in $.cleanData if jQuery UI not present (it already does similar).
     * This snippet is taken directly from jQuery UI source code found here:
     *     http://code.jquery.com/ui/jquery-ui-git.js
     */
    if (!$.ui) {
      $["cleanData" + replaceSuffix] = $.cleanData;
      $.cleanData = function (elems) {
        for (var i = 0, elem; (elem = $(elems[i])).length; i++) {
          if (elem.attr(ATTR_HAS)) {
            try {
              elem.triggerHandler("removeqtip");
            } catch (e) {}
          }
        }
        $["cleanData" + replaceSuffix].apply(this, arguments);
      };
    } // qTip version

    QTIP.version = "@@VERSION";

    // Base ID for all qTips
    QTIP.nextid = 0;

    // Inactive events array
    QTIP.inactiveEvents = INACTIVE_EVENTS;

    // Base z-index for all qTips
    QTIP.zindex = 15000;

    // Define configuration defaults
    QTIP.defaults = {
      prerender: FALSE,
      id: FALSE,
      overwrite: TRUE,
      suppress: TRUE,
      content: {
        text: TRUE,
        attr: "title",
        title: FALSE,
        button: FALSE,
      },
      position: {
        my: "top left",
        at: "bottom right",
        target: FALSE,
        container: FALSE,
        viewport: FALSE,
        adjust: {
          x: 0,
          y: 0,
          mouse: TRUE,
          scroll: TRUE,
          resize: TRUE,
          method: "flipinvert flipinvert",
        },
        effect: function (api, pos, viewport) {
          $(this).animate(pos, {
            duration: 200,
            queue: FALSE,
          });
        },
      },
      show: {
        target: FALSE,
        event: "mouseenter",
        effect: TRUE,
        delay: 90,
        solo: FALSE,
        ready: FALSE,
        autofocus: FALSE,
      },
      hide: {
        target: FALSE,
        event: "mouseleave",
        effect: TRUE,
        delay: 0,
        fixed: FALSE,
        inactive: FALSE,
        leave: "window",
        distance: FALSE,
      },
      style: {
        classes: "",
        widget: FALSE,
        width: FALSE,
        height: FALSE,
        def: TRUE,
      },
      events: {
        render: NULL,
        move: NULL,
        show: NULL,
        hide: NULL,
        toggle: NULL,
        visible: NULL,
        hidden: NULL,
        focus: NULL,
        blur: NULL,
      },
    };

    var TIP,
      // .bind()/.on() namespace
      TIPNS = ".qtip-tip",
      // Common CSS strings
      MARGIN = "margin",
      BORDER = "border",
      COLOR = "color",
      BG_COLOR = "background-color",
      TRANSPARENT = "transparent",
      IMPORTANT = " !important",
      // Check if the browser supports <canvas/> elements
      HASCANVAS = !!document.createElement("canvas").getContext,
      // Invalid colour values used in parseColours()
      INVALID = /rgba?\(0, 0, 0(, 0)?\)|transparent|#123456/i;

    // Camel-case method, taken from jQuery source
    // http://code.jquery.com/jquery-1.8.0.js
    function camel(s) {
      return s.charAt(0).toUpperCase() + s.slice(1);
    }

    /*
     * Modified from Modernizr's testPropsAll()
     * http://modernizr.com/downloads/modernizr-latest.js
     */
    var cssProps = {},
      cssPrefixes = ["Webkit", "O", "Moz", "ms"];
    function vendorCss(elem, prop) {
      var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1),
        props = (prop + " " + cssPrefixes.join(ucProp + " ") + ucProp).split(
          " "
        ),
        cur,
        val,
        i = 0;

      // If the property has already been mapped...
      if (cssProps[prop]) {
        return elem.css(cssProps[prop]);
      }

      while ((cur = props[i++])) {
        if ((val = elem.css(cur)) !== undefined) {
          return (cssProps[prop] = cur), val;
        }
      }
    }

    // Parse a given elements CSS property into an int
    function intCss(elem, prop) {
      return Math.ceil(parseFloat(vendorCss(elem, prop)));
    }

    // VML creation (for IE only)
    if (!HASCANVAS) {
      var createVML = function (tag, props, style) {
        return (
          "<qtipvml:" +
          tag +
          ' xmlns="urn:schemas-microsoft.com:vml" class="qtip-vml" ' +
          (props || "") +
          ' style="behavior: url(#default#VML); ' +
          (style || "") +
          '" />'
        );
      };
    }

    // Canvas only definitions
    else {
      var PIXEL_RATIO = window.devicePixelRatio || 1,
        BACKING_STORE_RATIO = (function () {
          var context = document.createElement("canvas").getContext("2d");
          return (
            context.backingStorePixelRatio ||
            context.webkitBackingStorePixelRatio ||
            context.mozBackingStorePixelRatio ||
            context.msBackingStorePixelRatio ||
            context.oBackingStorePixelRatio ||
            1
          );
        })(),
        SCALE = PIXEL_RATIO / BACKING_STORE_RATIO;
    }

    function Tip(qtip, options) {
      this._ns = "tip";
      this.options = options;
      this.offset = options.offset;
      this.size = [options.width, options.height];

      // Initialize
      this.init((this.qtip = qtip));
    }

    $.extend(Tip.prototype, {
      init: function (qtip) {
        var context, tip;

        // Create tip element and prepend to the tooltip
        tip =
          this.element =
          qtip.elements.tip =
            $("<div />", { class: NAMESPACE + "-tip" }).prependTo(qtip.tooltip);

        // Create tip drawing element(s)
        if (HASCANVAS) {
          // save() as soon as we create the canvas element so FF2 doesn't bork on our first restore()!
          context = $("<canvas />").appendTo(this.element)[0].getContext("2d");

          // Setup constant parameters
          context.lineJoin = "miter";
          context.miterLimit = 100000;
          context.save();
        } else {
          context = createVML(
            "shape",
            'coordorigin="0,0"',
            "position:absolute;"
          );
          this.element.html(context + context);

          // Prevent mousing down on the tip since it causes problems with .live() handling in IE due to VML
          qtip._bind(
            $("*", tip).add(tip),
            ["click", "mousedown"],
            function (event) {
              event.stopPropagation();
            },
            this._ns
          );
        }

        // Bind update events
        qtip._bind(
          qtip.tooltip,
          "tooltipmove",
          this.reposition,
          this._ns,
          this
        );

        // Create it
        this.create();
      },

      _swapDimensions: function () {
        this.size[0] = this.options.height;
        this.size[1] = this.options.width;
      },
      _resetDimensions: function () {
        this.size[0] = this.options.width;
        this.size[1] = this.options.height;
      },

      _useTitle: function (corner) {
        var titlebar = this.qtip.elements.titlebar;
        return (
          titlebar &&
          (corner.y === TOP ||
            (corner.y === CENTER &&
              this.element.position().top +
                this.size[1] / 2 +
                this.options.offset <
                titlebar.outerHeight(TRUE)))
        );
      },

      _parseCorner: function (corner) {
        var my = this.qtip.options.position.my;

        // Detect corner and mimic properties
        if (corner === FALSE || my === FALSE) {
          corner = FALSE;
        } else if (corner === TRUE) {
          corner = new CORNER(my.string());
        } else if (!corner.string) {
          corner = new CORNER(corner);
          corner.fixed = TRUE;
        }

        return corner;
      },

      _parseWidth: function (corner, side, use) {
        var elements = this.qtip.elements,
          prop = BORDER + camel(side) + "Width";

        return (
          (use
            ? intCss(use, prop)
            : intCss(elements.content, prop) ||
              intCss(
                (this._useTitle(corner) && elements.titlebar) ||
                  elements.content,
                prop
              ) ||
              intCss(elements.tooltip, prop)) || 0
        );
      },

      _parseRadius: function (corner) {
        var elements = this.qtip.elements,
          prop = BORDER + camel(corner.y) + camel(corner.x) + "Radius";

        return BROWSER.ie < 9
          ? 0
          : intCss(
              (this._useTitle(corner) && elements.titlebar) || elements.content,
              prop
            ) ||
              intCss(elements.tooltip, prop) ||
              0;
      },

      _invalidColour: function (elem, prop, compare) {
        var val = elem.css(prop);
        return !val ||
          (compare && val === elem.css(compare)) ||
          INVALID.test(val)
          ? FALSE
          : val;
      },

      _parseColours: function (corner) {
        var elements = this.qtip.elements,
          tip = this.element.css("cssText", ""),
          borderSide = BORDER + camel(corner[corner.precedance]) + camel(COLOR),
          colorElem =
            (this._useTitle(corner) && elements.titlebar) || elements.content,
          css = this._invalidColour,
          color = [];

        // Attempt to detect the background colour from various elements, left-to-right precedance
        color[0] =
          css(tip, BG_COLOR) ||
          css(colorElem, BG_COLOR) ||
          css(elements.content, BG_COLOR) ||
          css(elements.tooltip, BG_COLOR) ||
          tip.css(BG_COLOR);

        // Attempt to detect the correct border side colour from various elements, left-to-right precedance
        color[1] =
          css(tip, borderSide, COLOR) ||
          css(colorElem, borderSide, COLOR) ||
          css(elements.content, borderSide, COLOR) ||
          css(elements.tooltip, borderSide, COLOR) ||
          elements.tooltip.css(borderSide);

        // Reset background and border colours
        $("*", tip)
          .add(tip)
          .css(
            "cssText",
            BG_COLOR +
              ":" +
              TRANSPARENT +
              IMPORTANT +
              ";" +
              BORDER +
              ":0" +
              IMPORTANT +
              ";"
          );

        return color;
      },

      _calculateSize: function (corner) {
        var y = corner.precedance === Y,
          width = this.options["width"],
          height = this.options["height"],
          isCenter = corner.abbrev() === "c",
          base = (y ? width : height) * (isCenter ? 0.5 : 1),
          pow = Math.pow,
          round = Math.round,
          bigHyp,
          ratio,
          result,
          smallHyp = Math.sqrt(pow(base, 2) + pow(height, 2)),
          hyp = [
            (this.border / base) * smallHyp,
            (this.border / height) * smallHyp,
          ];

        hyp[2] = Math.sqrt(pow(hyp[0], 2) - pow(this.border, 2));
        hyp[3] = Math.sqrt(pow(hyp[1], 2) - pow(this.border, 2));

        bigHyp = smallHyp + hyp[2] + hyp[3] + (isCenter ? 0 : hyp[0]);
        ratio = bigHyp / smallHyp;

        result = [round(ratio * width), round(ratio * height)];
        return y ? result : result.reverse();
      },

      // Tip coordinates calculator
      _calculateTip: function (corner, size, scale) {
        scale = scale || 1;
        size = size || this.size;

        var width = size[0] * scale,
          height = size[1] * scale,
          width2 = Math.ceil(width / 2),
          height2 = Math.ceil(height / 2),
          // Define tip coordinates in terms of height and width values
          tips = {
            br: [0, 0, width, height, width, 0],
            bl: [0, 0, width, 0, 0, height],
            tr: [0, height, width, 0, width, height],
            tl: [0, 0, 0, height, width, height],
            tc: [0, height, width2, 0, width, height],
            bc: [0, 0, width, 0, width2, height],
            rc: [0, 0, width, height2, 0, height],
            lc: [width, 0, width, height, 0, height2],
          };

        // Set common side shapes
        tips.lt = tips.br;
        tips.rt = tips.bl;
        tips.lb = tips.tr;
        tips.rb = tips.tl;

        return tips[corner.abbrev()];
      },

      // Tip coordinates drawer (canvas)
      _drawCoords: function (context, coords) {
        context.beginPath();
        context.moveTo(coords[0], coords[1]);
        context.lineTo(coords[2], coords[3]);
        context.lineTo(coords[4], coords[5]);
        context.closePath();
      },

      create: function () {
        // Determine tip corner
        var c = (this.corner =
          (HASCANVAS || BROWSER.ie) && this._parseCorner(this.options.corner));

        // If we have a tip corner...
        if ((this.enabled = !!this.corner && this.corner.abbrev() !== "c")) {
          // Cache it
          this.qtip.cache.corner = c.clone();

          // Create it
          this.update();
        }

        // Toggle tip element
        this.element.toggle(this.enabled);

        return this.corner;
      },

      update: function (corner, position) {
        if (!this.enabled) {
          return this;
        }

        var elements = this.qtip.elements,
          tip = this.element,
          inner = tip.children(),
          options = this.options,
          curSize = this.size,
          mimic = options.mimic,
          round = Math.round,
          color,
          precedance,
          context,
          coords,
          bigCoords,
          translate,
          newSize,
          border,
          BACKING_STORE_RATIO;

        // Re-determine tip if not already set
        if (!corner) {
          corner = this.qtip.cache.corner || this.corner;
        }

        // Use corner property if we detect an invalid mimic value
        if (mimic === FALSE) {
          mimic = corner;
        }

        // Otherwise inherit mimic properties from the corner object as necessary
        else {
          mimic = new CORNER(mimic);
          mimic.precedance = corner.precedance;

          if (mimic.x === "inherit") {
            mimic.x = corner.x;
          } else if (mimic.y === "inherit") {
            mimic.y = corner.y;
          } else if (mimic.x === mimic.y) {
            mimic[corner.precedance] = corner[corner.precedance];
          }
        }
        precedance = mimic.precedance;

        // Ensure the tip width.height are relative to the tip position
        if (corner.precedance === X) {
          this._swapDimensions();
        } else {
          this._resetDimensions();
        }

        // Update our colours
        color = this.color = this._parseColours(corner);

        // Detect border width, taking into account colours
        if (color[1] !== TRANSPARENT) {
          // Grab border width
          border = this.border = this._parseWidth(
            corner,
            corner[corner.precedance]
          );

          // If border width isn't zero, use border color as fill if it's not invalid (1.0 style tips)
          if (options.border && border < 1 && !INVALID.test(color[1])) {
            color[0] = color[1];
          }

          // Set border width (use detected border width if options.border is true)
          this.border = border =
            options.border !== TRUE ? options.border : border;
        }

        // Border colour was invalid, set border to zero
        else {
          this.border = border = 0;
        }

        // Determine tip size
        newSize = this.size = this._calculateSize(corner);
        tip.css({
          width: newSize[0],
          height: newSize[1],
          lineHeight: newSize[1] + "px",
        });

        // Calculate tip translation
        if (corner.precedance === Y) {
          translate = [
            round(
              mimic.x === LEFT
                ? border
                : mimic.x === RIGHT
                ? newSize[0] - curSize[0] - border
                : (newSize[0] - curSize[0]) / 2
            ),
            round(mimic.y === TOP ? newSize[1] - curSize[1] : 0),
          ];
        } else {
          translate = [
            round(mimic.x === LEFT ? newSize[0] - curSize[0] : 0),
            round(
              mimic.y === TOP
                ? border
                : mimic.y === BOTTOM
                ? newSize[1] - curSize[1] - border
                : (newSize[1] - curSize[1]) / 2
            ),
          ];
        }

        // Canvas drawing implementation
        if (HASCANVAS) {
          // Grab canvas context and clear/save it
          context = inner[0].getContext("2d");
          context.restore();
          context.save();
          context.clearRect(0, 0, 6000, 6000);

          // Calculate coordinates
          coords = this._calculateTip(mimic, curSize, SCALE);
          bigCoords = this._calculateTip(mimic, this.size, SCALE);

          // Set the canvas size using calculated size
          inner
            .attr(WIDTH, newSize[0] * SCALE)
            .attr(HEIGHT, newSize[1] * SCALE);
          inner.css(WIDTH, newSize[0]).css(HEIGHT, newSize[1]);

          // Draw the outer-stroke tip
          this._drawCoords(context, bigCoords);
          context.fillStyle = color[1];
          context.fill();

          // Draw the actual tip
          context.translate(translate[0] * SCALE, translate[1] * SCALE);
          this._drawCoords(context, coords);
          context.fillStyle = color[0];
          context.fill();
        }

        // VML (IE Proprietary implementation)
        else {
          // Calculate coordinates
          coords = this._calculateTip(mimic);

          // Setup coordinates string
          coords =
            "m" +
            coords[0] +
            "," +
            coords[1] +
            " l" +
            coords[2] +
            "," +
            coords[3] +
            " " +
            coords[4] +
            "," +
            coords[5] +
            " xe";

          // Setup VML-specific offset for pixel-perfection
          translate[2] =
            border && /^(r|b)/i.test(corner.string())
              ? BROWSER.ie === 8
                ? 2
                : 1
              : 0;

          // Set initial CSS
          inner
            .css({
              coordsize: newSize[0] + border + " " + (newSize[1] + border),
              antialias: "" + (mimic.string().indexOf(CENTER) > -1),
              left: translate[0] - translate[2] * Number(precedance === X),
              top: translate[1] - translate[2] * Number(precedance === Y),
              width: newSize[0] + border,
              height: newSize[1] + border,
            })
            .each(function (i) {
              var $this = $(this);

              // Set shape specific attributes
              $this[$this.prop ? "prop" : "attr"]({
                coordsize: newSize[0] + border + " " + (newSize[1] + border),
                path: coords,
                fillcolor: color[0],
                filled: !!i,
                stroked: !i,
              }).toggle(!!(border || i));

              // Check if border is enabled and add stroke element
              !i &&
                $this.html(
                  createVML(
                    "stroke",
                    'weight="' +
                      border * 2 +
                      'px" color="' +
                      color[1] +
                      '" miterlimit="1000" joinstyle="miter"'
                  )
                );
            });
        }

        // Opera bug #357 - Incorrect tip position
        // https://github.com/Craga89/qTip2/issues/367
        window.opera &&
          setTimeout(function () {
            elements.tip.css({
              display: "inline-block",
              visibility: "visible",
            });
          }, 1);

        // Position if needed
        if (position !== FALSE) {
          this.calculate(corner, newSize);
        }
      },

      calculate: function (corner, size) {
        if (!this.enabled) {
          return FALSE;
        }

        var self = this,
          elements = this.qtip.elements,
          tip = this.element,
          userOffset = this.options.offset,
          isWidget = elements.tooltip.hasClass("ui-widget"),
          position = {},
          precedance,
          corners;

        // Inherit corner if not provided
        corner = corner || this.corner;
        precedance = corner.precedance;

        // Determine which tip dimension to use for adjustment
        size = size || this._calculateSize(corner);

        // Setup corners and offset array
        corners = [corner.x, corner.y];
        if (precedance === X) {
          corners.reverse();
        }

        // Calculate tip position
        $.each(corners, function (i, side) {
          var b, bc, br;

          if (side === CENTER) {
            b = precedance === Y ? LEFT : TOP;
            position[b] = "50%";
            position[MARGIN + "-" + b] =
              -Math.round(size[precedance === Y ? 0 : 1] / 2) + userOffset;
          } else {
            b = self._parseWidth(corner, side, elements.tooltip);
            bc = self._parseWidth(corner, side, elements.content);
            br = self._parseRadius(corner);

            position[side] = Math.max(
              -self.border,
              i ? bc : userOffset + (br > b ? br : -b)
            );
          }
        });

        // Adjust for tip size
        position[corner[precedance]] -= size[precedance === X ? 0 : 1];

        // Set and return new position
        tip
          .css({ margin: "", top: "", bottom: "", left: "", right: "" })
          .css(position);
        return position;
      },

      reposition: function (event, api, pos, viewport) {
        if (!this.enabled) {
          return;
        }

        var cache = api.cache,
          newCorner = this.corner.clone(),
          adjust = pos.adjusted,
          method = api.options.position.adjust.method.split(" "),
          horizontal = method[0],
          vertical = method[1] || method[0],
          shift = { left: FALSE, top: FALSE, x: 0, y: 0 },
          offset,
          css = {},
          props;

        function shiftflip(direction, precedance, popposite, side, opposite) {
          // Horizontal - Shift or flip method
          if (
            direction === SHIFT &&
            newCorner.precedance === precedance &&
            adjust[side] &&
            newCorner[popposite] !== CENTER
          ) {
            newCorner.precedance = newCorner.precedance === X ? Y : X;
          } else if (direction !== SHIFT && adjust[side]) {
            newCorner[precedance] =
              newCorner[precedance] === CENTER
                ? adjust[side] > 0
                  ? side
                  : opposite
                : newCorner[precedance] === side
                ? opposite
                : side;
          }
        }

        function shiftonly(xy, side, opposite) {
          if (newCorner[xy] === CENTER) {
            css[MARGIN + "-" + side] = shift[xy] =
              offset[MARGIN + "-" + side] - adjust[side];
          } else {
            props =
              offset[opposite] !== undefined
                ? [adjust[side], -offset[side]]
                : [-adjust[side], offset[side]];

            if ((shift[xy] = Math.max(props[0], props[1])) > props[0]) {
              pos[side] -= adjust[side];
              shift[side] = FALSE;
            }

            css[offset[opposite] !== undefined ? opposite : side] = shift[xy];
          }
        }

        // If our tip position isn't fixed e.g. doesn't adjust with viewport...
        if (this.corner.fixed !== TRUE) {
          // Perform shift/flip adjustments
          shiftflip(horizontal, X, Y, LEFT, RIGHT);
          shiftflip(vertical, Y, X, TOP, BOTTOM);

          // Update and redraw the tip if needed (check cached details of last drawn tip)
          if (
            newCorner.string() !== cache.corner.string() &&
            (cache.cornerTop !== adjust.top || cache.cornerLeft !== adjust.left)
          ) {
            this.update(newCorner, FALSE);
          }
        }

        // Setup tip offset properties
        offset = this.calculate(newCorner);

        // Readjust offset object to make it left/top
        if (offset.right !== undefined) {
          offset.left = -offset.right;
        }
        if (offset.bottom !== undefined) {
          offset.top = -offset.bottom;
        }
        offset.user = this.offset;

        // Perform shift adjustments
        if ((shift.left = horizontal === SHIFT && !!adjust.left)) {
          shiftonly(X, LEFT, RIGHT);
        }
        if ((shift.top = vertical === SHIFT && !!adjust.top)) {
          shiftonly(Y, TOP, BOTTOM);
        }

        /*
         * If the tip is adjusted in both dimensions, or in a
         * direction that would cause it to be anywhere but the
         * outer border, hide it!
         */
        this.element
          .css(css)
          .toggle(
            !(
              (shift.x && shift.y) ||
              (newCorner.x === CENTER && shift.y) ||
              (newCorner.y === CENTER && shift.x)
            )
          );

        // Adjust position to accomodate tip dimensions
        pos.left -= offset.left.charAt
          ? offset.user
          : horizontal !== SHIFT || shift.top || (!shift.left && !shift.top)
          ? offset.left + this.border
          : 0;
        pos.top -= offset.top.charAt
          ? offset.user
          : vertical !== SHIFT || shift.left || (!shift.left && !shift.top)
          ? offset.top + this.border
          : 0;

        // Cache details
        cache.cornerLeft = adjust.left;
        cache.cornerTop = adjust.top;
        cache.corner = newCorner.clone();
      },

      destroy: function () {
        // Unbind events
        this.qtip._unbind(this.qtip.tooltip, this._ns);

        // Remove the tip element(s)
        if (this.qtip.elements.tip) {
          this.qtip.elements.tip.find("*").remove().end().remove();
        }
      },
    });

    TIP = PLUGINS.tip = function (api) {
      return new Tip(api, api.options.style.tip);
    };

    // Initialize tip on render
    TIP.initialize = "render";

    // Setup plugin sanitization options
    TIP.sanitize = function (options) {
      if (options.style && "tip" in options.style) {
        var opts = options.style.tip;
        if (typeof opts !== "object") {
          opts = options.style.tip = { corner: opts };
        }
        if (!/string|boolean/i.test(typeof opts.corner)) {
          opts.corner = TRUE;
        }
      }
    };

    // Add new option checks for the plugin
    CHECKS.tip = {
      "^position.my|style.tip.(corner|mimic|border)$": function () {
        // Make sure a tip can be drawn
        this.create();

        // Reposition the tooltip
        this.qtip.reposition();
      },
      "^style.tip.(height|width)$": function (obj) {
        // Re-set dimensions and redraw the tip
        this.size = [obj.width, obj.height];
        this.update();

        // Reposition the tooltip
        this.qtip.reposition();
      },
      "^content.title|style.(classes|widget)$": function () {
        this.update();
      },
    };

    // Extend original qTip defaults
    $.extend(TRUE, QTIP.defaults, {
      style: {
        tip: {
          corner: TRUE,
          mimic: FALSE,
          width: 6,
          height: 6,
          border: TRUE,
          offset: 0,
        },
      },
    });

    var MODAL,
      OVERLAY,
      MODALCLASS = "qtip-modal",
      MODALSELECTOR = "." + MODALCLASS;

    OVERLAY = function () {
      var self = this,
        focusableElems = {},
        current,
        onLast,
        prevState,
        elem;

      // Modified code from jQuery UI 1.10.0 source
      // http://code.jquery.com/ui/1.10.0/jquery-ui.js
      function focusable(element) {
        // Use the defined focusable checker when possible
        if ($.expr[":"].focusable) {
          return $.expr[":"].focusable;
        }

        var isTabIndexNotNaN = !isNaN($.attr(element, "tabindex")),
          nodeName = element.nodeName && element.nodeName.toLowerCase(),
          map,
          mapName,
          img;

        if ("area" === nodeName) {
          map = element.parentNode;
          mapName = map.name;
          if (
            !element.href ||
            !mapName ||
            map.nodeName.toLowerCase() !== "map"
          ) {
            return false;
          }
          img = $("img[usemap=#" + mapName + "]")[0];
          return !!img && img.is(":visible");
        }
        return /input|select|textarea|button|object/.test(nodeName)
          ? !element.disabled
          : "a" === nodeName
          ? element.href || isTabIndexNotNaN
          : isTabIndexNotNaN;
      }

      // Focus inputs using cached focusable elements (see update())
      function focusInputs(blurElems) {
        // Blurring body element in IE causes window.open windows to unfocus!
        if (focusableElems.length < 1 && blurElems.length) {
          blurElems.not("body").blur();
        }

        // Focus the inputs
        else {
          focusableElems.first().focus();
        }
      }

      // Steal focus from elements outside tooltip
      function stealFocus(event) {
        if (!elem.is(":visible")) {
          return;
        }

        var target = $(event.target),
          tooltip = current.tooltip,
          container = target.closest(SELECTOR),
          targetOnTop;

        // Determine if input container target is above this
        targetOnTop =
          container.length < 1
            ? FALSE
            : parseInt(container[0].style.zIndex, 10) >
              parseInt(tooltip[0].style.zIndex, 10);

        // If we're showing a modal, but focus has landed on an input below
        // this modal, divert focus to the first visible input in this modal
        // or if we can't find one... the tooltip itself
        if (!targetOnTop && target.closest(SELECTOR)[0] !== tooltip[0]) {
          focusInputs(target);
        }

        // Detect when we leave the last focusable element...
        onLast = event.target === focusableElems[focusableElems.length - 1];
      }

      $.extend(self, {
        init: function () {
          // Create document overlay
          elem = self.elem = $("<div />", {
            id: "qtip-overlay",
            html: "<div></div>",
            mousedown: function () {
              return FALSE;
            },
          }).hide();

          // Make sure we can't focus anything outside the tooltip
          $(document.body).bind("focusin" + MODALSELECTOR, stealFocus);

          // Apply keyboard "Escape key" close handler
          $(document).bind("keydown" + MODALSELECTOR, function (event) {
            if (
              current &&
              current.options.show.modal.escape &&
              event.keyCode === 27
            ) {
              current.hide(event);
            }
          });

          // Apply click handler for blur option
          elem.bind("click" + MODALSELECTOR, function (event) {
            if (current && current.options.show.modal.blur) {
              current.hide(event);
            }
          });

          return self;
        },

        update: function (api) {
          // Update current API reference
          current = api;

          // Update focusable elements if enabled
          if (api.options.show.modal.stealfocus !== FALSE) {
            focusableElems = api.tooltip.find("*").filter(function () {
              return focusable(this);
            });
          } else {
            focusableElems = [];
          }
        },

        toggle: function (api, state, duration) {
          var docBody = $(document.body),
            tooltip = api.tooltip,
            options = api.options.show.modal,
            effect = options.effect,
            type = state ? "show" : "hide",
            visible = elem.is(":visible"),
            visibleModals = $(MODALSELECTOR)
              .filter(":visible:not(:animated)")
              .not(tooltip),
            zindex;

          // Set active tooltip API reference
          self.update(api);

          // If the modal can steal the focus...
          // Blur the current item and focus anything in the modal we an
          if (state && options.stealfocus !== FALSE) {
            focusInputs($(":focus"));
          }

          // Toggle backdrop cursor style on show
          elem.toggleClass("blurs", options.blur);

          // Append to body on show
          if (state) {
            elem.appendTo(document.body);
          }

          // Prevent modal from conflicting with show.solo, and don't hide backdrop is other modals are visible
          if (
            (elem.is(":animated") &&
              visible === state &&
              prevState !== FALSE) ||
            (!state && visibleModals.length)
          ) {
            return self;
          }

          // Stop all animations
          elem.stop(TRUE, FALSE);

          // Use custom function if provided
          if ($.isFunction(effect)) {
            effect.call(elem, state);
          }

          // If no effect type is supplied, use a simple toggle
          else if (effect === FALSE) {
            elem[type]();
          }

          // Use basic fade function
          else {
            elem.fadeTo(
              parseInt(duration, 10) || 90,
              state ? 1 : 0,
              function () {
                if (!state) {
                  elem.hide();
                }
              }
            );
          }

          // Reset position and detach from body on hide
          if (!state) {
            elem.queue(function (next) {
              elem.css({ left: "", top: "" });
              if (!$(MODALSELECTOR).length) {
                elem.detach();
              }
              next();
            });
          }

          // Cache the state
          prevState = state;

          // If the tooltip is destroyed, set reference to null
          if (current.destroyed) {
            current = NULL;
          }

          return self;
        },
      });

      self.init();
    };
    OVERLAY = new OVERLAY();

    function Modal(api, options) {
      this.options = options;
      this._ns = "-modal";

      this.init((this.qtip = api));
    }

    $.extend(Modal.prototype, {
      init: function (qtip) {
        var tooltip = qtip.tooltip;

        // If modal is disabled... return
        if (!this.options.on) {
          return this;
        }

        // Set overlay reference
        qtip.elements.overlay = OVERLAY.elem;

        // Add unique attribute so we can grab modal tooltips easily via a SELECTOR, and set z-index
        tooltip
          .addClass(MODALCLASS)
          .css("z-index", QTIP.modal_zindex + $(MODALSELECTOR).length);

        // Apply our show/hide/focus modal events
        qtip._bind(
          tooltip,
          ["tooltipshow", "tooltiphide"],
          function (event, api, duration) {
            var oEvent = event.originalEvent;

            // Make sure mouseout doesn't trigger a hide when showing the modal and mousing onto backdrop
            if (event.target === tooltip[0]) {
              if (
                oEvent &&
                event.type === "tooltiphide" &&
                /mouse(leave|enter)/.test(oEvent.type) &&
                $(oEvent.relatedTarget).closest(OVERLAY.elem[0]).length
              ) {
                try {
                  event.preventDefault();
                } catch (e) {}
              } else if (!oEvent || (oEvent && oEvent.type !== "tooltipsolo")) {
                this.toggle(event, event.type === "tooltipshow", duration);
              }
            }
          },
          this._ns,
          this
        );

        // Adjust modal z-index on tooltip focus
        qtip._bind(
          tooltip,
          "tooltipfocus",
          function (event, api) {
            // If focus was cancelled before it reached us, don't do anything
            if (event.isDefaultPrevented() || event.target !== tooltip[0]) {
              return;
            }

            var qtips = $(MODALSELECTOR),
              // Keep the modal's lower than other, regular qtips
              newIndex = QTIP.modal_zindex + qtips.length,
              curIndex = parseInt(tooltip[0].style.zIndex, 10);

            // Set overlay z-index
            OVERLAY.elem[0].style.zIndex = newIndex - 1;

            // Reduce modal z-index's and keep them properly ordered
            qtips.each(function () {
              if (this.style.zIndex > curIndex) {
                this.style.zIndex -= 1;
              }
            });

            // Fire blur event for focused tooltip
            qtips.filter("." + CLASS_FOCUS).qtip("blur", event.originalEvent);

            // Set the new z-index
            tooltip.addClass(CLASS_FOCUS)[0].style.zIndex = newIndex;

            // Set current
            OVERLAY.update(api);

            // Prevent default handling
            try {
              event.preventDefault();
            } catch (e) {}
          },
          this._ns,
          this
        );

        // Focus any other visible modals when this one hides
        qtip._bind(
          tooltip,
          "tooltiphide",
          function (event) {
            if (event.target === tooltip[0]) {
              $(MODALSELECTOR)
                .filter(":visible")
                .not(tooltip)
                .last()
                .qtip("focus", event);
            }
          },
          this._ns,
          this
        );
      },

      toggle: function (event, state, duration) {
        // Make sure default event hasn't been prevented
        if (event && event.isDefaultPrevented()) {
          return this;
        }

        // Toggle it
        OVERLAY.toggle(this.qtip, !!state, duration);
      },

      destroy: function () {
        // Remove modal class
        this.qtip.tooltip.removeClass(MODALCLASS);

        // Remove bound events
        this.qtip._unbind(this.qtip.tooltip, this._ns);

        // Delete element reference
        OVERLAY.toggle(this.qtip, FALSE);
        delete this.qtip.elements.overlay;
      },
    });

    MODAL = PLUGINS.modal = function (api) {
      return new Modal(api, api.options.show.modal);
    };

    // Setup sanitiztion rules
    MODAL.sanitize = function (opts) {
      if (opts.show) {
        if (typeof opts.show.modal !== "object") {
          opts.show.modal = { on: !!opts.show.modal };
        } else if (typeof opts.show.modal.on === "undefined") {
          opts.show.modal.on = TRUE;
        }
      }
    };

    // Base z-index for all modal tooltips (use qTip core z-index as a base)
    QTIP.modal_zindex = QTIP.zindex - 200;

    // Plugin needs to be initialized on render
    MODAL.initialize = "render";

    // Setup option set checks
    CHECKS.modal = {
      "^show.modal.(on|blur)$": function () {
        // Initialise
        this.destroy();
        this.init();

        // Show the modal if not visible already and tooltip is visible
        this.qtip.elems.overlay.toggle(this.qtip.tooltip[0].offsetWidth > 0);
      },
    };

    // Extend original api defaults
    $.extend(TRUE, QTIP.defaults, {
      show: {
        modal: {
          on: FALSE,
          effect: TRUE,
          blur: TRUE,
          stealfocus: TRUE,
          escape: TRUE,
        },
      },
    });
    PLUGINS.viewport = function (
      api,
      position,
      posOptions,
      targetWidth,
      targetHeight,
      elemWidth,
      elemHeight
    ) {
      var target = posOptions.target,
        tooltip = api.elements.tooltip,
        my = posOptions.my,
        at = posOptions.at,
        adjust = posOptions.adjust,
        method = adjust.method.split(" "),
        methodX = method[0],
        methodY = method[1] || method[0],
        viewport = posOptions.viewport,
        container = posOptions.container,
        cache = api.cache,
        adjusted = { left: 0, top: 0 },
        fixed,
        newMy,
        newClass,
        containerOffset,
        containerStatic,
        viewportWidth,
        viewportHeight,
        viewportScroll,
        viewportOffset;

      // If viewport is not a jQuery element, or it's the window/document, or no adjustment method is used... return
      if (
        !viewport.jquery ||
        target[0] === window ||
        target[0] === document.body ||
        adjust.method === "none"
      ) {
        return adjusted;
      }

      // Cach container details
      containerOffset = container.offset() || adjusted;
      containerStatic = container.css("position") === "static";

      // Cache our viewport details
      fixed = tooltip.css("position") === "fixed";
      viewportWidth =
        viewport[0] === window ? viewport.width() : viewport.outerWidth(FALSE);
      viewportHeight =
        viewport[0] === window
          ? viewport.height()
          : viewport.outerHeight(FALSE);
      viewportScroll = {
        left: fixed ? 0 : viewport.scrollLeft(),
        top: fixed ? 0 : viewport.scrollTop(),
      };
      viewportOffset = viewport.offset() || adjusted;

      // Generic calculation method
      function calculate(
        side,
        otherSide,
        type,
        adjust,
        side1,
        side2,
        lengthName,
        targetLength,
        elemLength
      ) {
        var initialPos = position[side1],
          mySide = my[side],
          atSide = at[side],
          isShift = type === SHIFT,
          myLength =
            mySide === side1
              ? elemLength
              : mySide === side2
              ? -elemLength
              : -elemLength / 2,
          atLength =
            atSide === side1
              ? targetLength
              : atSide === side2
              ? -targetLength
              : -targetLength / 2,
          sideOffset =
            viewportScroll[side1] +
            viewportOffset[side1] -
            (containerStatic ? 0 : containerOffset[side1]),
          overflow1 = sideOffset - initialPos,
          overflow2 =
            initialPos +
            elemLength -
            (lengthName === WIDTH ? viewportWidth : viewportHeight) -
            sideOffset,
          offset =
            myLength -
            (my.precedance === side || mySide === my[otherSide]
              ? atLength
              : 0) -
            (atSide === CENTER ? targetLength / 2 : 0);

        // shift
        if (isShift) {
          offset = (mySide === side1 ? 1 : -1) * myLength;

          // Adjust position but keep it within viewport dimensions
          position[side1] +=
            overflow1 > 0 ? overflow1 : overflow2 > 0 ? -overflow2 : 0;
          position[side1] = Math.max(
            -containerOffset[side1] + viewportOffset[side1],
            initialPos - offset,
            Math.min(
              Math.max(
                -containerOffset[side1] +
                  viewportOffset[side1] +
                  (lengthName === WIDTH ? viewportWidth : viewportHeight),
                initialPos + offset
              ),
              position[side1],

              // Make sure we don't adjust complete off the element when using 'center'
              mySide === "center" ? initialPos - myLength : 1e9
            )
          );
        }

        // flip/flipinvert
        else {
          // Update adjustment amount depending on if using flipinvert or flip
          adjust *= type === FLIPINVERT ? 2 : 0;

          // Check for overflow on the left/top
          if (overflow1 > 0 && (mySide !== side1 || overflow2 > 0)) {
            position[side1] -= offset + adjust;
            newMy.invert(side, side1);
          }

          // Check for overflow on the bottom/right
          else if (overflow2 > 0 && (mySide !== side2 || overflow1 > 0)) {
            position[side1] -= (mySide === CENTER ? -offset : offset) + adjust;
            newMy.invert(side, side2);
          }

          // Make sure we haven't made things worse with the adjustment and reset if so
          if (
            position[side1] < viewportScroll &&
            -position[side1] > overflow2
          ) {
            position[side1] = initialPos;
            newMy = my.clone();
          }
        }

        return position[side1] - initialPos;
      }

      // Set newMy if using flip or flipinvert methods
      if (methodX !== "shift" || methodY !== "shift") {
        newMy = my.clone();
      }

      // Adjust position based onviewport and adjustment options
      adjusted = {
        left:
          methodX !== "none"
            ? calculate(
                X,
                Y,
                methodX,
                adjust.x,
                LEFT,
                RIGHT,
                WIDTH,
                targetWidth,
                elemWidth
              )
            : 0,
        top:
          methodY !== "none"
            ? calculate(
                Y,
                X,
                methodY,
                adjust.y,
                TOP,
                BOTTOM,
                HEIGHT,
                targetHeight,
                elemHeight
              )
            : 0,
      };

      // Set tooltip position class if it's changed
      if (
        newMy &&
        cache.lastClass !== (newClass = NAMESPACE + "-pos-" + newMy.abbrev())
      ) {
        tooltip
          .removeClass(api.cache.lastClass)
          .addClass((api.cache.lastClass = newClass));
      }

      return adjusted;
    };
    PLUGINS.polys = {
      // POLY area coordinate calculator
      //	Special thanks to Ed Cradock for helping out with this.
      //	Uses a binary search algorithm to find suitable coordinates.
      polygon: function (baseCoords, corner) {
        var result = {
            width: 0,
            height: 0,
            position: {
              top: 1e10,
              right: 0,
              bottom: 0,
              left: 1e10,
            },
            adjustable: FALSE,
          },
          i = 0,
          next,
          coords = [],
          compareX = 1,
          compareY = 1,
          realX = 0,
          realY = 0,
          newWidth,
          newHeight;

        // First pass, sanitize coords and determine outer edges
        i = baseCoords.length;
        while (i--) {
          next = [
            parseInt(baseCoords[--i], 10),
            parseInt(baseCoords[i + 1], 10),
          ];

          if (next[0] > result.position.right) {
            result.position.right = next[0];
          }
          if (next[0] < result.position.left) {
            result.position.left = next[0];
          }
          if (next[1] > result.position.bottom) {
            result.position.bottom = next[1];
          }
          if (next[1] < result.position.top) {
            result.position.top = next[1];
          }

          coords.push(next);
        }

        // Calculate height and width from outer edges
        newWidth = result.width = Math.abs(
          result.position.right - result.position.left
        );
        newHeight = result.height = Math.abs(
          result.position.bottom - result.position.top
        );

        // If it's the center corner...
        if (corner.abbrev() === "c") {
          result.position = {
            left: result.position.left + result.width / 2,
            top: result.position.top + result.height / 2,
          };
        } else {
          // Second pass, use a binary search algorithm to locate most suitable coordinate
          while (
            newWidth > 0 &&
            newHeight > 0 &&
            compareX > 0 &&
            compareY > 0
          ) {
            newWidth = Math.floor(newWidth / 2);
            newHeight = Math.floor(newHeight / 2);

            if (corner.x === LEFT) {
              compareX = newWidth;
            } else if (corner.x === RIGHT) {
              compareX = result.width - newWidth;
            } else {
              compareX += Math.floor(newWidth / 2);
            }

            if (corner.y === TOP) {
              compareY = newHeight;
            } else if (corner.y === BOTTOM) {
              compareY = result.height - newHeight;
            } else {
              compareY += Math.floor(newHeight / 2);
            }

            i = coords.length;
            while (i--) {
              if (coords.length < 2) {
                break;
              }

              realX = coords[i][0] - result.position.left;
              realY = coords[i][1] - result.position.top;

              if (
                (corner.x === LEFT && realX >= compareX) ||
                (corner.x === RIGHT && realX <= compareX) ||
                (corner.x === CENTER &&
                  (realX < compareX || realX > result.width - compareX)) ||
                (corner.y === TOP && realY >= compareY) ||
                (corner.y === BOTTOM && realY <= compareY) ||
                (corner.y === CENTER &&
                  (realY < compareY || realY > result.height - compareY))
              ) {
                coords.splice(i, 1);
              }
            }
          }
          result.position = { left: coords[0][0], top: coords[0][1] };
        }

        return result;
      },

      rect: function (ax, ay, bx, by) {
        return {
          width: Math.abs(bx - ax),
          height: Math.abs(by - ay),
          position: {
            left: Math.min(ax, bx),
            top: Math.min(ay, by),
          },
        };
      },

      _angles: {
        tc: 3 / 2,
        tr: 7 / 4,
        tl: 5 / 4,
        bc: 1 / 2,
        br: 1 / 4,
        bl: 3 / 4,
        rc: 2,
        lc: 1,
        c: 0,
      },
      ellipse: function (cx, cy, rx, ry, corner) {
        var c = PLUGINS.polys._angles[corner.abbrev()],
          rxc = c === 0 ? 0 : rx * Math.cos(c * Math.PI),
          rys = ry * Math.sin(c * Math.PI);

        return {
          width: rx * 2 - Math.abs(rxc),
          height: ry * 2 - Math.abs(rys),
          position: {
            left: cx + rxc,
            top: cy + rys,
          },
          adjustable: FALSE,
        };
      },
      circle: function (cx, cy, r, corner) {
        return PLUGINS.polys.ellipse(cx, cy, r, r, corner);
      },
    };
    PLUGINS.svg = function (api, svg, corner) {
      var doc = $(document),
        elem = svg[0],
        root = $(elem.ownerSVGElement),
        xScale = 1,
        yScale = 1,
        complex = true,
        rootWidth,
        rootHeight,
        mtx,
        transformed,
        viewBox,
        len,
        next,
        i,
        points,
        result,
        position,
        dimensions;

      // Ascend the parentNode chain until we find an element with getBBox()
      while (!elem.getBBox) {
        elem = elem.parentNode;
      }
      if (!elem.getBBox || !elem.parentNode) {
        return FALSE;
      }

      // Determine dimensions where possible
      rootWidth =
        root.attr("width") || root.width() || parseInt(root.css("width"), 10);
      rootHeight =
        root.attr("height") ||
        root.height() ||
        parseInt(root.css("height"), 10);

      // Add stroke characteristics to scaling
      var strokeWidth2 = (parseInt(svg.css("stroke-width"), 10) || 0) / 2;
      if (strokeWidth2) {
        xScale += strokeWidth2 / rootWidth;
        yScale += strokeWidth2 / rootHeight;
      }

      // Determine which shape calculation to use
      switch (elem.nodeName) {
        case "ellipse":
        case "circle":
          result = PLUGINS.polys.ellipse(
            elem.cx.baseVal.value,
            elem.cy.baseVal.value,
            (elem.rx || elem.r).baseVal.value + strokeWidth2,
            (elem.ry || elem.r).baseVal.value + strokeWidth2,
            corner
          );
          break;

        case "line":
        case "polygon":
        case "polyline":
          // Determine points object (line has none, so mimic using array)
          points = elem.points || [
            { x: elem.x1.baseVal.value, y: elem.y1.baseVal.value },
            { x: elem.x2.baseVal.value, y: elem.y2.baseVal.value },
          ];

          for (
            result = [], i = -1, len = points.numberOfItems || points.length;
            ++i < len;

          ) {
            next = points.getItem ? points.getItem(i) : points[i];
            result.push.apply(result, [next.x, next.y]);
          }

          result = PLUGINS.polys.polygon(result, corner);
          break;

        // Unknown shape or rectangle? Use bounding box
        default:
          result = elem.getBoundingClientRect();
          result = {
            width: result.width,
            height: result.height,
            position: {
              left: result.left,
              top: result.top,
            },
          };
          complex = false;
          break;
      }

      // Shortcut assignments
      position = result.position;
      root = root[0];

      // If the shape was complex (i.e. not using bounding box calculations)
      if (complex) {
        // Convert position into a pixel value
        if (root.createSVGPoint) {
          mtx = elem.getScreenCTM();
          points = root.createSVGPoint();

          points.x = position.left;
          points.y = position.top;
          transformed = points.matrixTransform(mtx);
          position.left = transformed.x;
          position.top = transformed.y;
        }

        // Calculate viewBox characteristics
        if (
          root.viewBox &&
          (viewBox = root.viewBox.baseVal) &&
          viewBox.width &&
          viewBox.height
        ) {
          xScale *= rootWidth / viewBox.width;
          yScale *= rootHeight / viewBox.height;
        }
      }

      // Adjust by scroll offset
      position.left += doc.scrollLeft();
      position.top += doc.scrollTop();

      return result;
    };
    PLUGINS.imagemap = function (api, area, corner, adjustMethod) {
      if (!area.jquery) {
        area = $(area);
      }

      var shape = area.attr("shape").toLowerCase().replace("poly", "polygon"),
        image = $('img[usemap="#' + area.parent("map").attr("name") + '"]'),
        coordsString = $.trim(area.attr("coords")),
        coordsArray = coordsString.replace(/,$/, "").split(","),
        imageOffset,
        coords,
        i,
        next,
        result,
        len;

      // If we can't find the image using the map...
      if (!image.length) {
        return FALSE;
      }

      // Pass coordinates string if polygon
      if (shape === "polygon") {
        result = PLUGINS.polys.polygon(coordsArray, corner);
      }

      // Otherwise parse the coordinates and pass them as arguments
      else if (PLUGINS.polys[shape]) {
        for (i = -1, len = coordsArray.length, coords = []; ++i < len; ) {
          coords.push(parseInt(coordsArray[i], 10));
        }

        result = PLUGINS.polys[shape].apply(this, coords.concat(corner));
      }

      // If no shapre calculation method was found, return false
      else {
        return FALSE;
      }

      // Make sure we account for padding and borders on the image
      imageOffset = image.offset();
      imageOffset.left += Math.ceil(
        (image.outerWidth(FALSE) - image.width()) / 2
      );
      imageOffset.top += Math.ceil(
        (image.outerHeight(FALSE) - image.height()) / 2
      );

      // Add image position to offset coordinates
      result.position.left += imageOffset.left;
      result.position.top += imageOffset.top;

      return result;
    };
    var IE6,
      /*
       * BGIFrame adaption (http://plugins.jquery.com/project/bgiframe)
       * Special thanks to Brandon Aaron
       */
      BGIFRAME =
        '<iframe class="qtip-bgiframe" frameborder="0" tabindex="-1" src="javascript:\'\';" ' +
        ' style="display:block; position:absolute; z-index:-1; filter:alpha(opacity=0); ' +
        '-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";"></iframe>';

    function Ie6(api, qtip) {
      this._ns = "ie6";
      this.init((this.qtip = api));
    }

    $.extend(Ie6.prototype, {
      _scroll: function () {
        var overlay = this.qtip.elements.overlay;
        overlay && (overlay[0].style.top = $(window).scrollTop() + "px");
      },

      init: function (qtip) {
        var tooltip = qtip.tooltip,
          scroll;

        // Create the BGIFrame element if needed
        if ($("select, object").length < 1) {
          this.bgiframe = qtip.elements.bgiframe =
            $(BGIFRAME).appendTo(tooltip);

          // Update BGIFrame on tooltip move
          qtip._bind(
            tooltip,
            "tooltipmove",
            this.adjustBGIFrame,
            this._ns,
            this
          );
        }

        // redraw() container for width/height calculations
        this.redrawContainer = $("<div/>", {
          id: NAMESPACE + "-rcontainer",
        }).appendTo(document.body);

        // Fixup modal plugin if present too
        if (
          qtip.elements.overlay &&
          qtip.elements.overlay.addClass("qtipmodal-ie6fix")
        ) {
          qtip._bind(
            window,
            ["scroll", "resize"],
            this._scroll,
            this._ns,
            this
          );
          qtip._bind(tooltip, ["tooltipshow"], this._scroll, this._ns, this);
        }

        // Set dimensions
        this.redraw();
      },

      adjustBGIFrame: function () {
        var tooltip = this.qtip.tooltip,
          dimensions = {
            height: tooltip.outerHeight(FALSE),
            width: tooltip.outerWidth(FALSE),
          },
          plugin = this.qtip.plugins.tip,
          tip = this.qtip.elements.tip,
          tipAdjust,
          offset;

        // Adjust border offset
        offset = parseInt(tooltip.css("borderLeftWidth"), 10) || 0;
        offset = { left: -offset, top: -offset };

        // Adjust for tips plugin
        if (plugin && tip) {
          tipAdjust =
            plugin.corner.precedance === "x" ? [WIDTH, LEFT] : [HEIGHT, TOP];
          offset[tipAdjust[1]] -= tip[tipAdjust[0]]();
        }

        // Update bgiframe
        this.bgiframe.css(offset).css(dimensions);
      },

      // Max/min width simulator function
      redraw: function () {
        if (this.qtip.rendered < 1 || this.drawing) {
          return this;
        }

        var tooltip = this.qtip.tooltip,
          style = this.qtip.options.style,
          container = this.qtip.options.position.container,
          perc,
          width,
          max,
          min;

        // Set drawing flag
        this.qtip.drawing = 1;

        // If tooltip has a set height/width, just set it... like a boss!
        if (style.height) {
          tooltip.css(HEIGHT, style.height);
        }
        if (style.width) {
          tooltip.css(WIDTH, style.width);
        }

        // Simulate max/min width if not set width present...
        else {
          // Reset width and add fluid class
          tooltip.css(WIDTH, "").appendTo(this.redrawContainer);

          // Grab our tooltip width (add 1 if odd so we don't get wrapping problems.. huzzah!)
          width = tooltip.width();
          if (width % 2 < 1) {
            width += 1;
          }

          // Grab our max/min properties
          max = tooltip.css("maxWidth") || "";
          min = tooltip.css("minWidth") || "";

          // Parse into proper pixel values
          perc = (max + min).indexOf("%") > -1 ? container.width() / 100 : 0;
          max = (max.indexOf("%") > -1 ? perc : 1) * parseInt(max, 10) || width;
          min = (min.indexOf("%") > -1 ? perc : 1) * parseInt(min, 10) || 0;

          // Determine new dimension size based on max/min/current values
          width = max + min ? Math.min(Math.max(width, min), max) : width;

          // Set the newly calculated width and remvoe fluid class
          tooltip.css(WIDTH, Math.round(width)).appendTo(container);
        }

        // Set drawing flag
        this.drawing = 0;

        return this;
      },

      destroy: function () {
        // Remove iframe
        this.bgiframe && this.bgiframe.remove();

        // Remove bound events
        this.qtip._unbind([window, this.qtip.tooltip], this._ns);
      },
    });

    IE6 = PLUGINS.ie6 = function (api) {
      // Proceed only if the browser is IE6
      return BROWSER.ie === 6 ? new Ie6(api) : FALSE;
    };

    IE6.initialize = "render";

    CHECKS.ie6 = {
      "^content|style$": function () {
        this.redraw();
      },
    };
  });
})(window, document);
